diff --git a/admin/settings/appearance.php b/admin/settings/appearance.php index 67d824af0f7c8..7bca9fd4fb7e2 100644 --- a/admin/settings/appearance.php +++ b/admin/settings/appearance.php @@ -115,6 +115,49 @@ $ADMIN->add('appearance', new admin_externalpage('resetemoticons', new lang_string('emoticonsreset', 'admin'), new moodle_url('/admin/resetemoticons.php'), 'moodle/site:config', true)); + + // The "media" subpage. + $temp = new admin_settingpage('mediasettings', get_string('mediasettings', 'core_media')); + + $temp->add(new admin_setting_heading('mediaformats', get_string('mediaformats', 'core_media'), + format_text(get_string('mediaformats_desc', 'core_media'), FORMAT_MARKDOWN))); + + // External services. + $temp->add(new admin_setting_configcheckbox('core_media_enable_youtube', + get_string('siteyoutube', 'core_media'), get_string('siteyoutube_desc', 'core_media'), 1)); + $temp->add(new admin_setting_configcheckbox('core_media_enable_vimeo', + get_string('sitevimeo', 'core_media'), get_string('sitevimeo_desc', 'core_media'), 0)); + + // Options which require Flash. + $temp->add(new admin_setting_configcheckbox('core_media_enable_mp3', + get_string('mp3audio', 'core_media'), get_string('mp3audio_desc', 'core_media'), 1)); + $temp->add(new admin_setting_configcheckbox('core_media_enable_flv', + get_string('flashvideo', 'core_media'), get_string('flashvideo_desc', 'core_media'), 1)); + $temp->add(new admin_setting_configcheckbox('core_media_enable_swf', + get_string('flashanimation', 'core_media'), get_string('flashanimation_desc', 'core_media'), 1)); + + // HTML 5 media. + // Audio now enabled by default so that it can provide a fallback for mp3 on devices without flash. + $temp->add(new admin_setting_configcheckbox('core_media_enable_html5audio', + get_string('html5audio', 'core_media'), get_string('html5audio_desc', 'core_media'), 1)); + // Video now enabled by default so it can provide mp4 support. + $temp->add(new admin_setting_configcheckbox('core_media_enable_html5video', + get_string('html5video', 'core_media'), get_string('html5video_desc', 'core_media'), 1)); + + // Legacy players. + $temp->add(new admin_setting_heading('legacymediaformats', + get_string('legacyheading', 'core_media'), get_string('legacyheading_desc', 'core_media'))); + + $temp->add(new admin_setting_configcheckbox('core_media_enable_qt', + get_string('legacyquicktime', 'core_media'), get_string('legacyquicktime_desc', 'core_media'), 1)); + $temp->add(new admin_setting_configcheckbox('core_media_enable_wmp', + get_string('legacywmp', 'core_media'), get_string('legacywmp_desc', 'core_media'), 1)); + $temp->add(new admin_setting_configcheckbox('core_media_enable_rm', + get_string('legacyreal', 'core_media'), get_string('legacyreal_desc', 'core_media'), 1)); + + $ADMIN->add('appearance', $temp); + + // "documentation" settingpage $temp = new admin_settingpage('documentation', new lang_string('moodledocs')); $temp->add(new admin_setting_configtext('docroot', new lang_string('docroot', 'admin'), new lang_string('configdocroot', 'admin'), 'http://docs.moodle.org', PARAM_URL)); diff --git a/filter/mediaplugin/db/install.php b/filter/mediaplugin/db/install.php index 2489fac3a5ce7..1200eb6bfd87f 100644 --- a/filter/mediaplugin/db/install.php +++ b/filter/mediaplugin/db/install.php @@ -1,5 +1,4 @@ get_manager(); + if ($oldversion < 2011121200) { + // Move all the media enable setttings that are now handled by core media renderer. + foreach (array('html5video', 'html5audio', 'mp3', 'flv', 'wmp', 'qt', 'rm', + 'youtube', 'vimeo', 'swf') as $type) { + $existingkey = 'filter_mediaplugin_enable_' . $type; + if (array_key_exists($existingkey, $CFG)) { + set_config('core_media_enable_' . $type, $CFG->{$existingkey}); + unset_config($existingkey); + } + } + + // Override setting for html5 to turn it on (previous default was off; because + // of changes in the way fallbacks are handled, this is now unlikely to cause + // a problem, and is required for mobile a/v support on non-Flash devices, so + // this change is basically needed in order to maintain existing behaviour). + set_config('core_media_enable_html5video', 1); + set_config('core_media_enable_html5audio', 1); + + upgrade_plugin_savepoint(true, 2011121200, 'filter', 'mediaplugin'); + } + return true; } diff --git a/filter/mediaplugin/dev/perftest.php b/filter/mediaplugin/dev/perftest.php new file mode 100644 index 0000000000000..e590b396d06e2 --- /dev/null +++ b/filter/mediaplugin/dev/perftest.php @@ -0,0 +1,173 @@ +. + +/** + * Media filter performance test script. + * + * For developer test usage only. This can be used to compare performance if + * there are changes to the system in future. + * + * @copyright 2012 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @package filter_mediaplugin + */ + +require(dirname(__FILE__) . '/../../../config.php'); +require_once($CFG->dirroot . '/filter/mediaplugin/filter.php'); + +// Only available to site admins. +require_login(); +if (!is_siteadmin()) { + print_error('nopermissions', 'error', '', 'perftest'); +} + +// Set up page. +$PAGE->set_context(context_system::instance()); +$PAGE->set_url(new moodle_url('/filter/mediaplugin/perftest.php')); +$PAGE->set_heading($SITE->fullname); +print $OUTPUT->header(); + +// Hack setup to enable all players. +$CFG->core_media_enable_youtube = 1; +$CFG->core_media_enable_vimeo = 1; +$CFG->core_media_enable_mp3 = 1; +$CFG->core_media_enable_flv = 1; +$CFG->core_media_enable_swf = 1; +$CFG->core_media_enable_html5audio = 1; +$CFG->core_media_enable_html5video = 1; +$CFG->core_media_enable_qt = 1; +$CFG->core_media_enable_wmp = 1; +$CFG->core_media_enable_rm = 1; + +$CFG->filter_mediaplugin_enable_youtube = 1; +$CFG->filter_mediaplugin_enable_vimeo = 1; +$CFG->filter_mediaplugin_enable_mp3 = 1; +$CFG->filter_mediaplugin_enable_flv = 1; +$CFG->filter_mediaplugin_enable_swf = 1; +$CFG->filter_mediaplugin_enable_html5audio = 1; +$CFG->filter_mediaplugin_enable_html5video = 1; +$CFG->filter_mediaplugin_enable_qt = 1; +$CFG->filter_mediaplugin_enable_wmp = 1; +$CFG->filter_mediaplugin_enable_rm = 1; + +// Create plugin. +$filterplugin = new filter_mediaplugin(null, array()); + +// Note: As this is a developer test page, language strings are not used: all +// text is English-only. + +/** + * Starts time counter. + */ +function filter_mediaplugin_perf_start() { + global $filter_mediaplugin_starttime; + $filter_mediaplugin_starttime = microtime(true); +} + +/** + * Ends and displays time counter. + * @param string $name Counter name to display + */ +function filter_mediaplugin_perf_stop($name) { + global $filter_mediaplugin_starttime; + $time = microtime(true) - $filter_mediaplugin_starttime; + + echo html_writer::tag('li', $name . ': ' . html_writer::tag('strong', round($time, 2)) . 'ms'); +} + +// 1) Some sample text strings. +// Note: These are from a random sample of real forum data. Just in case there +// are any privacy concerns I have altered names as may be clear. +$samples = array( + "

Hi,

\n

I've got myself 2 Heaney's \"The Burial at Thebes\"

", + "best mark iv heard so far v v good", + "

I have a script draft anyone want to look at it?", + "

Thanks for your input Legolas and Ghimli!

", + "

Just to say that I'm thinking of those of you who are working on TMA02.

", + "

1. If someone asks you 'where do you come from?'

", + "

With regards to Aragorn's question 'what would we do different'?

\n", + "

Just thought I'd drop a line to see how everyone is managing generally?

\n", + "

Feb '12 - Oct '12  AA100

\n

Nov '12 - April '13 - A150

\n", + "

So where does that leave the bible???

", +); + +// 2) Combine sample text strings into one really big (20KB) string. +$length = 0; +$bigstring = ''; +$index = 0; +while ($length < 20 * 1024) { + $bigstring .= $samples[$index]; + $length += strlen($samples[$index]); + $index++; + if ($index >= count($samples)) { + $index = 0; + } +} + +// 3) Make random samples from this. I did the following stats on recent forum +// posts: +// 0-199 characters approx 30% +// 200-1999 approx 60% +// 2000-19999 approx 10%. + +$samplebank = array(); +foreach (array(100 => 300, 1000 => 600, 10000 => 100) as $chars => $num) { + for ($i = 0; $i < $num; $i++) { + $start = rand(0, $length - $chars - 1); + $samplebank[] = substr($bigstring, $start, $chars); + } +} + +echo html_writer::start_tag('ul'); + +// First test: filter text that doesn't have any links. +filter_mediaplugin_perf_start(); +foreach ($samplebank as $sample) { + $filterplugin->filter($sample); +} +filter_mediaplugin_perf_stop('No links'); + +// Second test: filter text with one link added (that doesn't match). +$link = 'Link'; +$linksamples = array(); +foreach ($samplebank as $sample) { + // Make it the same length but with $link replacing the end part. + $linksamples[] = substr($sample, 0, -strlen($link)) . $link; +} + +filter_mediaplugin_perf_start(); +foreach ($linksamples as $sample) { + $filterplugin->filter($sample); +} +filter_mediaplugin_perf_stop('One link (no match)'); + +// Third test: filter text with one link added that does match (mp3). +$link = 'MP3 audio'; +$linksamples = array(); +foreach ($samplebank as $sample) { + // Make it the same length but with $link replacing the end part. + $linksamples[] = substr($sample, 0, -strlen($link)) . $link; +} + +filter_mediaplugin_perf_start(); +foreach ($linksamples as $sample) { + $filterplugin->filter($sample); +} +filter_mediaplugin_perf_stop('One link (mp3)'); + +// End page. +echo html_writer::end_tag('ul'); +print $OUTPUT->footer(); \ No newline at end of file diff --git a/filter/mediaplugin/filter.php b/filter/mediaplugin/filter.php index 2d9b6d8ccdd1c..57e58d513dc1d 100644 --- a/filter/mediaplugin/filter.php +++ b/filter/mediaplugin/filter.php @@ -28,31 +28,6 @@ defined('MOODLE_INTERNAL') || die(); -require_once($CFG->libdir.'/filelib.php'); - -if (!defined('FILTER_MEDIAPLUGIN_VIDEO_WIDTH')) { - /** - * Default media width, some plugins may use automatic sizes or accept resize parameters. - * This can be defined in config.php. - */ - define('FILTER_MEDIAPLUGIN_VIDEO_WIDTH', 400); -} - -if (!defined('FILTER_MEDIAPLUGIN_VIDEO_HEIGHT')) { - /** - * Default video height, plugins that know aspect ration - * should calculate it themselves using the FILTER_MEDIAPLUGIN_VIDEO_HEIGHT - * This can be defined in config.php. - */ - define('FILTER_MEDIAPLUGIN_VIDEO_HEIGHT', 300); -} - - -//TODO: we should use /u modifier in regex, unfortunately it may not work properly on some misconfigured servers, see lib/filter/urltolink/filter.php ... - -//TODO: we should migrate to proper config_plugin settings ... - - /** * Automatic media embedding filter class. * @@ -65,850 +40,93 @@ * @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; + /** @var core_media_renderer Media renderer */ + private $mediarenderer; + /** @var string Partial regex pattern indicating possible embeddable content */ + private $embedmarkers; - function filter($text, array $options = array()) { - global $CFG; + 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, '') === false) { - // performance shortcut - all regexes below end with the tag, - // if not present nothing can match + // Performance shortcut - if not tag, nothing can match. return $text; } - $newtext = $text; // we need to return the original value if regex fails! - - // YouTube and Vimeo are great because the files are not served by Moodle server - - if (!empty($CFG->filter_mediaplugin_enable_youtube)) { - $search = '/]*href="(https?:\/\/www\.youtube(-nocookie)?\.com)\/watch\?v=([a-z0-9\-_]+)[^"#]*(#d=([\d]{1,4})x([\d]{1,4}))?"[^>]*>([^>]*)<\/a>/is'; - $newtext = preg_replace_callback($search, 'filter_mediaplugin_youtube_callback', $newtext); - - $search = '/]*href="(https?:\/\/www\.youtube(-nocookie)?\.com)\/v\/([a-z0-9\-_]+)[^"#]*(#d=([\d]{1,4})x([\d]{1,4}))?[^>]*>([^>]*)<\/a>/is'; - $newtext = preg_replace_callback($search, 'filter_mediaplugin_youtube_callback', $newtext); - - $search = '/]*href="(https?:\/\/www\.youtube(-nocookie)?\.com)\/view_play_list\?p=([a-z0-9\-_]+)[^"#]*(#d=([\d]{1,4})x([\d]{1,4}))?[^>]*>([^>]*)<\/a>/is'; - $newtext = preg_replace_callback($search, 'filter_mediaplugin_youtube_playlist_callback', $newtext); - - $search = '/]*href="(https?:\/\/www\.youtube(-nocookie)?\.com)\/p\/([a-z0-9\-_]+)[^"#]*(#d=([\d]{1,4})x([\d]{1,4}))?[^>]*>([^>]*)<\/a>/is'; - $newtext = preg_replace_callback($search, 'filter_mediaplugin_youtube_playlist_callback', $newtext); - } - - if (!empty($CFG->filter_mediaplugin_enable_vimeo)) { - $search = '/]*href="http:\/\/vimeo\.com\/([0-9]+)[^"#]*(#d=([\d]{1,4})x([\d]{1,4}))?[^>]*>([^>]*)<\/a>/is'; - $newtext = preg_replace_callback($search, 'filter_mediaplugin_vimeo_callback', $newtext); - } - - - // HTML 5 audio and video tags are the future! If only if vendors decided to use just one audio and video format... - - if (!empty($CFG->filter_mediaplugin_enable_html5audio)) { - $search = '/]*href="([^"#\?]+\.(ogg|oga|aac|m4a)([#\?][^"]*)?)"[^>]*>([^>]*)<\/a>/is'; - $newtext = preg_replace_callback($search, 'filter_mediaplugin_html5audio_callback', $newtext); - } - - if (!empty($CFG->filter_mediaplugin_enable_html5video)) { - $search = '/]*href="([^"#\?]+\.(m4v|webm|ogv|mp4)([#\?][^"]*)?)"[^>]*>([^>]*)<\/a>/is'; - $newtext = preg_replace_callback($search, 'filter_mediaplugin_html5video_callback', $newtext); - } - - - // Flash stuff - - if (!empty($CFG->filter_mediaplugin_enable_mp3)) { - $search = '/]*href="([^"#\?]+\.mp3)"[^>]*>([^>]*)<\/a>/is'; - $newtext = preg_replace_callback($search, 'filter_mediaplugin_mp3_callback', $newtext); - } - - if ((!empty($options['noclean']) or !empty($CFG->allowobjectembed)) and !empty($CFG->filter_mediaplugin_enable_swf)) { - $search = '/]*href="([^"#\?]+\.swf)([#\?]d=([\d]{1,4})x([\d]{1,4}))?"[^>]*>([^>]*)<\/a>/is'; - $newtext = preg_replace_callback($search, 'filter_mediaplugin_swf_callback', $newtext); + if (!$this->mediarenderer) { + $this->mediarenderer = $PAGE->get_renderer('core', 'media'); + $this->embedmarkers = $this->mediarenderer->get_embeddable_markers(); } - if (!empty($CFG->filter_mediaplugin_enable_flv)) { - $search = '/]*href="([^"#\?]+\.(flv|f4v)([#\?][^"]*)?)"[^>]*>([^>]*)<\/a>/is'; - $newtext = preg_replace_callback($search, 'filter_mediaplugin_flv_callback', $newtext); - } - - - // The rest of legacy formats - these should not be used if possible - - if (!empty($CFG->filter_mediaplugin_enable_wmp)) { - $search = '/]*href="([^"#\?]+\.(wmv|avi))(\?d=([\d]{1,4})x([\d]{1,4}))?"[^>]*>([^>]*)<\/a>/is'; - $newtext = preg_replace_callback($search, 'filter_mediaplugin_wmp_callback', $newtext); - } - - if (!empty($CFG->filter_mediaplugin_enable_qt)) { - // HTML5 filtering may steal mpeg 4 formats - $search = '/]*href="([^"#\?]+\.(mpg|mpeg|mov|mp4|m4v|m4a))(\?d=([\d]{1,4})x([\d]{1,4}))?"[^>]*>([^>]*)<\/a>/is'; - $newtext = preg_replace_callback($search, 'filter_mediaplugin_qt_callback', $newtext); - } - - if (!empty($CFG->filter_mediaplugin_enable_rm)) { - // hopefully nobody is using this any more!! - // rpm is redhat packaging format these days, it is better to prevent these in default installs - - $search = '/]*href="([^"#\?]+\.(ra|ram|rm|rv))"[^>]*>([^>]*)<\/a>/is'; - $newtext = preg_replace_callback($search, 'filter_mediaplugin_real_callback', $newtext); - } + // Check SWF permissions. + $this->trusted = !empty($options['noclean']) or !empty($CFG->allowobjectembed); + // Handle all links that contain any 'embeddable' marker text (it could + // do all links, but the embeddable markers thing should make it faster + // by meaning for most links it doesn't drop into PHP code). + $newtext = preg_replace_callback($re = '~]*href="([^"]*(?:' . + $this->embedmarkers . ')[^"]*)"[^>]*>([^>]*)~is', + array($this, 'callback'), $text); if (empty($newtext) or $newtext === $text) { // error or not filtered - unset($newtext); return $text; } - return $newtext; } -} - - -///=========================== -/// utility functions - -/** - * Get mimetype of given url, useful for # alternative urls. - * - * @private - * @param string $url - * @return string $mimetype - */ -function filter_mediaplugin_get_mimetype($url) { - $matches = null; - if (preg_match("|^(.*)/[a-z]*file.php(\?file=)?(/[^&\?#]*)|", $url, $matches)) { - // remove the special moodle file serving hacks so that the *file.php is ignored - $url = $matches[1].$matches[3]; - } else { - $url = preg_replace('/[#\?].*$/', '', $url); - } - - $mimetype = mimeinfo('type', $url); - - return $mimetype; -} - -/** - * Parse list of alternative URLs - * @param string $url urls separated with '#', size specified as ?d=640x480 or #d=640x480 - * @param int $defaultwidth - * @param int $defaultheight - * @return array (urls, width, height) - */ -function filter_mediaplugin_parse_alternatives($url, $defaultwidth = 0, $defaultheight = 0) { - $urls = explode('#', $url); - $width = $defaultwidth; - $height = $defaultheight; - $returnurls = array(); - - foreach ($urls as $url) { - $matches = null; - - if (preg_match('/^d=([\d]{1,4})x([\d]{1,4})$/i', $url, $matches)) { // #d=640x480 - $width = $matches[1]; - $height = $matches[2]; - continue; - } - if (preg_match('/\?d=([\d]{1,4})x([\d]{1,4})$/i', $url, $matches)) { // old style file.ext?d=640x480 - $width = $matches[1]; - $height = $matches[2]; - $url = str_replace($matches[0], '', $url); - } - - $url = str_replace('&', '&', $url); - $url = clean_param($url, PARAM_URL); - if (empty($url)) { - continue; - } - - $returnurls[] = $url; - } - return array($returnurls, $width, $height); -} - -/** - * Should the current tag be ignored in this filter? - * @param string $tag - * @return bool - */ -function filter_mediaplugin_ignore($tag) { - if (preg_match('/class="[^"]*nomediaplugin/i', $tag)) { - return true; - } else { - false; - } -} - -///=========================== -/// callback filter functions - - -/** - * Replace audio links with audio tag. - * - * @param array $link - * @return string - */ -function filter_mediaplugin_html5audio_callback(array $link) { - global $CFG; - - if (filter_mediaplugin_ignore($link[0])) { - return $link[0]; - } - - $info = trim($link[4]); - if (empty($info) or strpos($info, 'http') === 0) { - $info = get_string('fallbackaudio', 'filter_mediaplugin'); - } - - list($urls, $ignorewidth, $ignoredheight) = filter_mediaplugin_parse_alternatives($link[1]); - - $fallbackurl = null; - $fallbackmime = null; - $sources = array(); - $fallbacklink = null; - - foreach ($urls as $url) { - $mimetype = filter_mediaplugin_get_mimetype($url); - if (strpos($mimetype, 'audio/') !== 0) { - continue; + /** + * Replace link with embedded content, if supported. + * + * @param array $matches + * @return string + */ + private function callback(array $matches) { + global $CFG, $PAGE; + // Check if we ignore it. + if (preg_match('/class="[^"]*nomediaplugin/i', $matches[0])) { + return $matches[0]; } - $sources[] = html_writer::tag('source', '', array('src' => $url, 'type' => $mimetype)); - if ($fallbacklink === null) { - $fallbacklink = html_writer::link($url.'#', $info); // the extra '#' prevents linking in mp3 filter below + // Get name. + $name = trim($matches[2]); + if (empty($name) or strpos($name, 'http') === 0) { + $name = ''; // Use default name. } - if ($fallbackurl === null) { - if ($mimetype === 'audio/mp3' or $mimetype === 'audio/aac') { - $fallbackurl = str_replace('&', '&', $url); - $fallbackmime = $mimetype; - } - } - } - if (!$sources) { - return $link[0]; - } - - if ($fallbackmime !== null) { - // fallback to quicktime - $fallback = << - - - - - - - - $fallbacklink - - - - - - - - - - $fallbacklink - - - -OET; - } else { - $fallback = $fallbacklink; - } - - $sources = implode("\n", $sources); - $title = s($info); - // audio players are supposed to be inline elements - $output = << -$sources -$fallback - -OET; - - return $output; -} + // Split provided URL into alternatives. + $urls = core_media::split_alternatives($matches[1], $width, $height); -/** - * Replace ogg video links with video tag. - * - * Please note this is not going to work in all browsers, - * it is also not xhtml strict. - * - * @param array $link - * @return string - */ -function filter_mediaplugin_html5video_callback(array $link) { + $options = array(); - if (filter_mediaplugin_ignore($link[0])) { - return $link[0]; - } - - $info = trim($link[4]); - if (empty($info) or strpos($info, 'http') === 0) { - $info = get_string('fallbackvideo', 'filter_mediaplugin'); - } - - list($urls, $width, $height) = filter_mediaplugin_parse_alternatives($link[1], FILTER_MEDIAPLUGIN_VIDEO_WIDTH, 0); - - $fallbackurl = null; - $fallbackmime = null; - $sources = array(); - $fallbacklink = null; - - foreach ($urls as $url) { - $mimetype = filter_mediaplugin_get_mimetype($url); - if (strpos($mimetype, 'video/') !== 0) { - continue; - } - $source = html_writer::tag('source', '', array('src' => $url, 'type' => $mimetype)); - if ($mimetype === 'video/mp4') { - // better add m4v as first source, it might be a bit more compatible with problematic browsers - array_unshift($sources, $source); - } else { - $sources[] = $source; + // Allow SWF (or not). + if ($this->trusted) { + $options[core_media::OPTION_TRUSTED] = true; } - if ($fallbacklink === null) { - $fallbacklink = html_writer::link($url.'#', $info); // the extra '#' prevents linking in mp3 filter below - } - if ($fallbackurl === null) { - if ($mimetype === 'video/mp4') { - $fallbackurl = str_replace('&', '&', $url); - $fallbackmime = $mimetype; - } - } - } - if (!$sources) { - return $link[0]; - } - - if ($fallbackmime !== null) { - $qtheight = ($height == 0) ? FILTER_MEDIAPLUGIN_VIDEO_HEIGHT : ($height + 15); - // fallback to quicktime - $fallback = << - - - - - - - - $fallbacklink - - - - - - - - - - $fallbacklink - - - -OET; - } else { - $fallback = $fallbacklink; - } - - $sources = implode("\n", $sources); - $title = s($info); - - if (empty($height)) { - // automatic height - $size = "width=\"$width\""; - } else { - $size = "width=\"$width\" height=\"$height\""; - } - - $output = << - - -OET; - - return $output; -} - -/** - * Replace mp3 links with small audio player. - * - * @param $link - * @return string - */ -function filter_mediaplugin_mp3_callback($link) { - static $count = 0; - - if (filter_mediaplugin_ignore($link[0])) { - return $link[0]; - } - - $count++; - $id = 'filter_mp3_'.time().'_'.$count; //we need something unique because it might be stored in text cache - - $url = $link[1]; - $rawurl = str_replace('&', '&', $url); - - $info = trim($link[2]); - if (empty($info) or strpos($info, 'http') === 0) { - $info = get_string('mp3audio', 'filter_mediaplugin'); - - } - $printlink = html_writer::link($rawurl, $info, array('class'=>'mediafallbacklink')); - - //note: when flash or javascript not available only the $printlink is displayed, - // audio players are supposed to be inline elements + // 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::OPTION_FALLBACK_TO_BLANK] = true; - $output = html_writer::tag('span', $printlink, array('id'=>$id, 'class'=>'mediaplugin mediaplugin_mp3')); - $output .= html_writer::script(js_writer::function_call('M.util.add_audio_player', array($id, $rawurl, true))); // we can not use standard JS init because this may be cached + // 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 = $this->mediarenderer->embed_alternatives($urls, $name, $width, $height, $options); - return $output; -} - -/** - * Replace swf links with embedded flash objects. - * - * Please note this is not a secure and is recommended to be disabled on production systems. - * - * @deprecated - * @param $link - * @return string - */ -function filter_mediaplugin_swf_callback($link) { - - if (filter_mediaplugin_ignore($link[0])) { - return $link[0]; - } - - $width = empty($link[3]) ? FILTER_MEDIAPLUGIN_VIDEO_WIDTH : $link[3]; - $height = empty($link[4]) ? FILTER_MEDIAPLUGIN_VIDEO_HEIGHT : $link[4]; - - $url = $link[1]; - $rawurl = str_replace('&', '&', $url); - - $info = trim($link[5]); - if (empty($info) or strpos($info, 'http') === 0) { - $info = get_string('flashanimation', 'filter_mediaplugin'); - - } - $printlink = html_writer::link($rawurl, $info, array('class'=>'mediafallbacklink')); - - $output = << - - - - - - - - - - - - - - - - - -$printlink - - - - - -OET; - - return $output; - -} - -/** - * Replace flv links with flow player. - * - * @param $link - * @return string - */ -function filter_mediaplugin_flv_callback($link) { - static $count = 0; - - if (filter_mediaplugin_ignore($link[0])) { - return $link[0]; - } - - $count++; - $id = 'filter_flv_'.time().'_'.$count; //we need something unique because it might be stored in text cache - - list($urls, $width, $height) = filter_mediaplugin_parse_alternatives($link[1], 0, 0); - - $autosize = false; - if (!$width and !$height) { - $width = FILTER_MEDIAPLUGIN_VIDEO_WIDTH; - $height = FILTER_MEDIAPLUGIN_VIDEO_HEIGHT; - $autosize = true; - } - - $flashurl = null; - $sources = array(); - - foreach ($urls as $url) { - $mimetype = filter_mediaplugin_get_mimetype($url); - if (strpos($mimetype, 'video/') !== 0) { - continue; - } - $source = html_writer::tag('source', '', array('src' => $url, 'type' => $mimetype)); - if ($mimetype === 'video/mp4') { - // better add m4v as first source, it might be a bit more compatible with problematic browsers - array_unshift($sources, $source); + // If something was embedded, return it, otherwise return original. + if ($result !== '') { + return $result; } else { - $sources[] = $source; - } - - if ($flashurl === null) { - $flashurl = $url; + return $matches[0]; } } - if (!$sources) { - return $link[0]; - } - - $info = trim($link[4]); - if (empty($info) or strpos($info, 'http') === 0) { - $info = get_string('fallbackvideo', 'filter_mediaplugin'); - } - $printlink = html_writer::link($flashurl.'#', $info, array('class'=>'mediafallbacklink')); // the '#' prevents the QT filter - - $title = s($info); - - if (count($sources) > 1) { - $sources = implode("\n", $sources); - - // html 5 fallback - $printlink = << -$sources -$printlink - - -OET; - } - - // note: no need to print "this is flv link" because it is printed automatically if JS or Flash not available - - $output = html_writer::tag('span', $printlink, array('id'=>$id, 'class'=>'mediaplugin mediaplugin_flv')); - $output .= html_writer::script(js_writer::function_call('M.util.add_video_player', array($id, addslashes_js($flashurl), $width, $height, $autosize))); // we can not use standard JS init because this may be cached - - return $output; } - -/** - * Replace real media links with real player. - * - * Note: hopefully nobody is using this obsolete format any more. - * - * @deprectated - * @param $link - * @return string - */ -function filter_mediaplugin_real_callback($link) { - - if (filter_mediaplugin_ignore($link[0])) { - return $link[0]; - } - - $url = $link[1]; - $rawurl = str_replace('&', '&', $url); - - //Note: the size is hardcoded intentionally because this does not work anyway! - - $width = FILTER_MEDIAPLUGIN_VIDEO_WIDTH; - $height = FILTER_MEDIAPLUGIN_VIDEO_HEIGHT; - - $info = trim($link[3]); - if (empty($info) or strpos($info, 'http') === 0) { - $info = get_string('fallbackvideo', 'filter_mediaplugin'); - } - $printlink = html_writer::link($rawurl, $info, array('class'=>'mediafallbacklink')); - - return << - $printlink
- - - - - - - - - - - - - -OET; -} - -/** - * Change links to YouTube into embedded YouTube videos - * - * Note: resizing via url is not supported, user can click the fullscreen button instead - * - * @param $link - * @return string - */ -function filter_mediaplugin_youtube_callback($link) { - global $CFG; - - if (filter_mediaplugin_ignore($link[0])) { - return $link[0]; - } - - $site = $link[1]; - $videoid = $link[3]; - - $info = trim($link[7]); - if (empty($info) or strpos($info, 'http') === 0) { - $info = get_string('siteyoutube', 'filter_mediaplugin'); - } - $info = s($info); - - $width = empty($link[5]) ? FILTER_MEDIAPLUGIN_VIDEO_WIDTH : $link[5]; - $height = empty($link[6]) ? FILTER_MEDIAPLUGIN_VIDEO_HEIGHT : $link[6]; - - if (empty($CFG->xmlstrictheaders)) { - return << -OET; - } - - //NOTE: we can not use any link fallback because it breaks built-in player on iOS devices - - $output = << - - - - - - -OET; - - return $output; -} - -/** - * Change YouTube playlist into embedded YouTube playlist videos - * - * Note: resizing via url is not supported, user can click the fullscreen button instead - * - * @param $link - * @return string - */ -function filter_mediaplugin_youtube_playlist_callback($link) { - global $CFG; - - if (filter_mediaplugin_ignore($link[0])) { - return $link[0]; - } - - $site = $link[1]; - $playlist = $link[3]; - - $info = trim($link[7]); - if (empty($info) or strpos($info, 'http') === 0) { - $info = get_string('siteyoutube', 'filter_mediaplugin'); - } - $printlink = html_writer::link("$site/view_play_list\?p=$playlist", $info, array('class'=>'mediafallbacklink')); - $info = s($info); - - $width = empty($link[5]) ? FILTER_MEDIAPLUGIN_VIDEO_WIDTH : $link[5]; - $height = empty($link[6]) ? FILTER_MEDIAPLUGIN_VIDEO_HEIGHT : $link[6]; - - // TODO: iframe HTML 5 video not implemented and object does work on iOS devices - - $output = << - - - - -$printlink - -OET; - - return $output; -} - -/** - * Change links to Vimeo into embedded Vimeo videos - * - * @param $link - * @return string - */ -function filter_mediaplugin_vimeo_callback($link) { - global $CFG; - - if (filter_mediaplugin_ignore($link[0])) { - return $link[0]; - } - - $videoid = $link[1]; - $info = s(strip_tags($link[5])); - - //Note: resizing via url is not supported, user can click the fullscreen button instead - // iframe embedding is not xhtml strict but it is the only option that seems to work on most devices - - $width = empty($link[3]) ? FILTER_MEDIAPLUGIN_VIDEO_WIDTH : $link[3]; - $height = empty($link[4]) ? FILTER_MEDIAPLUGIN_VIDEO_HEIGHT : $link[4]; - - $output = << - - -OET; - - return $output; -} - -/** - * Embed video using window media player if available - * - * This does not work much outside of IE, hopefully not many ppl use it these days. - * - * @param $link - * @return string - */ -function filter_mediaplugin_wmp_callback($link) { - - if (filter_mediaplugin_ignore($link[0])) { - return $link[0]; - } - - $url = $link[1]; - $rawurl = str_replace('&', '&', $url); - - $info = trim($link[6]); - if (empty($info) or strpos($info, 'http') === 0) { - $info = get_string('fallbackvideo', 'filter_mediaplugin'); - } - $printlink = html_writer::link($rawurl, $info, array('class'=>'mediafallbacklink')); - - if (empty($link[4]) or empty($link[5])) { - $mpsize = ''; - $size = 'width="'.FILTER_MEDIAPLUGIN_VIDEO_WIDTH.'" height="'.(FILTER_MEDIAPLUGIN_VIDEO_HEIGHT+64).'"'; - $autosize = 'true'; - } else { - $size = 'width="'.$link[4].'" height="'.($link[5] + 15).'"'; - $mpsize = 'width="'.$link[4].'" height="'.($link[5] + 64).'"'; - $autosize = 'false'; - } - $mimetype = filter_mediaplugin_get_mimetype($url); - - - - return << -$printlink
- - - - - - - - - - - - - - - - - - - - - - - - -OET; -} - -/** - * Replace quicktime links with quicktime player. - * - * You need to install a quicktime player, it is not available for all browsers+OS combinations. - * - * @param $link - * @return string - */ -function filter_mediaplugin_qt_callback($link) { - - if (filter_mediaplugin_ignore($link[0])) { - return $link[0]; - } - - $url = $link[1]; - $rawurl = str_replace('&', '&', $url); - - $info = trim($link[6]); - if (empty($info) or strpos($info, 'http') === 0) { - $info = get_string('fallbackvideo', 'filter_mediaplugin'); - } - $printlink = html_writer::link($rawurl, $info, array('class'=>'mediafallbacklink')); - - if (empty($link[4]) or empty($link[5])) { - $size = 'width="'.FILTER_MEDIAPLUGIN_VIDEO_WIDTH.'" height="'.(FILTER_MEDIAPLUGIN_VIDEO_HEIGHT+15).'"'; - } else { - $size = 'width="'.$link[4].'" height="'.($link[5]+15).'"'; - } - $mimetype = filter_mediaplugin_get_mimetype($url); - - // this is the safest fallback for incomplete or missing browser support for this format - return << -$printlink
- - - - - - - - - - - - - - - - - - - - -OET; -} - diff --git a/filter/mediaplugin/filtersettings.php b/filter/mediaplugin/filtersettings.php deleted file mode 100644 index 9e3ff298d99bf..0000000000000 --- a/filter/mediaplugin/filtersettings.php +++ /dev/null @@ -1,50 +0,0 @@ -. - -/** - * Mediaplugin filter settings - * - * @package filter - * @subpackage mediaplugin - * @copyright 2017 Petr Skoda (http://skodak.org) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die; - -if ($ADMIN->fulltree) { - - // External services - $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_youtube', get_string('siteyoutube','filter_mediaplugin'), get_string('siteyoutube_help','filter_mediaplugin'), 1)); - $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_vimeo', get_string('sitevimeo','filter_mediaplugin'), get_string('sitevimeo_help','filter_mediaplugin'), 0)); - - // these require flash - $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_mp3', get_string('mp3audio','filter_mediaplugin'), get_string('mp3audio_help','filter_mediaplugin'), 1)); - $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_flv', get_string('flashvideo','filter_mediaplugin'), get_string('flashvideo_help','filter_mediaplugin'), 1)); - $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_swf', get_string('flashanimation','filter_mediaplugin'), get_string('flashanimation_help','filter_mediaplugin'), 1)); - - // HTML 5 media - $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_html5audio', get_string('html5audio','filter_mediaplugin'), get_string('html5audio_help','filter_mediaplugin'), 0)); // disabled because mp3 is much better choice - $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_html5video', get_string('html5video','filter_mediaplugin'), get_string('html5video_help','filter_mediaplugin'), 0)); // disabled because flv with html5 fallback works better - - // legacy players - $settings->add(new admin_setting_heading('legacymediaformats', get_string('legacyheading', 'filter_mediaplugin'), get_string('legacyheading_help', 'filter_mediaplugin'))); - - $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_qt', get_string('legacyquicktime','filter_mediaplugin'), get_string('legacyquicktime_help','filter_mediaplugin'), 1)); - $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_wmp', get_string('legacywmp','filter_mediaplugin'), get_string('legacywmp_help','filter_mediaplugin'), 1)); - $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_rm', get_string('legacyreal','filter_mediaplugin'), get_string('legacyreal_help','filter_mediaplugin'), 1)); - -} diff --git a/filter/mediaplugin/lang/en/filter_mediaplugin.php b/filter/mediaplugin/lang/en/filter_mediaplugin.php index 94062fa9b1995..1a6c427501a62 100644 --- a/filter/mediaplugin/lang/en/filter_mediaplugin.php +++ b/filter/mediaplugin/lang/en/filter_mediaplugin.php @@ -25,28 +25,4 @@ $string['fallbackaudio'] = 'Audio link'; $string['fallbackvideo'] = 'Video link'; $string['filtername'] = 'Multimedia plugins'; -$string['flashanimation'] = 'Flash animation'; -$string['flashanimation_help'] = 'Files with extension *.swf. For security reasons this filter is used only in trusted texts.'; -$string['flashvideo'] = 'Flash video'; -$string['flashvideo_help'] = 'Files with extension *.flv and *.f4v. Plays video clips using Flowplayer, requires Flash plugin and javascript. Uses HTML 5 video fallback if multiple sources specified.'; -$string['html5audio'] = 'HTML 5 audio'; -$string['html5audio_help'] = 'Audio files with extension *.ogg, *.aac and others. It is compatible with latest web browsers only, unfortunately there is no format that is supported by all browsers. -Workaround is to specify fallbacks separated with # (ex: http://example.org/audio.aac#http://example.org/audio.aac#http://example.org/audio.mp3#), QuickTime player is used as a fallback for old browsers, fallback can be any audio type.'; -$string['html5video'] = 'HTML 5 video'; -$string['html5video_help'] = 'Video files with extension *.webm, *.m4v, *.ogv, *.mp4 and others. It is compatible with latest web browsers only, unfortunately there is no format that is supported by all browsers. -Workaround is to specify fallbacks sources separated with # (ex: http://example.org/video.m4v#http://example.org/video.aac#http://example.org/video.ogv#d=640x480), QuickTime player is used as a fallback for old browsers.'; -$string['mp3audio'] = 'MP3 audio'; -$string['mp3audio_help'] = 'Files with extension *.mp3. Plays audio using Flowplayer, requires Flash plugin.'; -$string['legacyquicktime'] = 'QuickTime player'; -$string['legacyquicktime_help'] = 'Files with extension *.mov, *.mp4, *.m4a, *.mp4 and *.mpg. Requires QuickTime player or codecs.'; -$string['legacyreal'] = 'Real media player'; -$string['legacyreal_help'] = 'Files with extension *.rm, *.ra, *.ram, *.rp, *.rv. Requires RealPlayer.'; -$string['legacywmp'] = 'Windows media player'; -$string['legacywmp_help'] = 'Files with extension *.avi and *.wmv. Fully compatible with Internet Explorer in Windows, may be problematic in other browsers or operating systems.'; -$string['legacyheading'] = 'Legacy media players'; -$string['legacyheading_help'] = 'Following formats are not recommended for general usage, they are usually used in intranet installation with centrally managed clients.'; -$string['sitevimeo'] = 'Vimeo'; -$string['sitevimeo_help'] = 'Vimeo video sharing site.'; -$string['siteyoutube'] = 'YouTube'; -$string['siteyoutube_help'] = 'YouTube video sharing site, video and playlist links supported.'; diff --git a/filter/mediaplugin/tests/filter_test.php b/filter/mediaplugin/tests/filter_test.php index 7749a20e07f56..86876102204d5 100644 --- a/filter/mediaplugin/tests/filter_test.php +++ b/filter/mediaplugin/tests/filter_test.php @@ -37,16 +37,16 @@ function test_filter_mediaplugin_link() { $this->resetAfterTest(true); // we need to enable the plugins somehow - $CFG->filter_mediaplugin_enable_youtube = 1; - $CFG->filter_mediaplugin_enable_vimeo = 1; - $CFG->filter_mediaplugin_enable_mp3 = 1; - $CFG->filter_mediaplugin_enable_flv = 1; - $CFG->filter_mediaplugin_enable_swf = 1; - $CFG->filter_mediaplugin_enable_html5audio = 1; - $CFG->filter_mediaplugin_enable_html5video = 1; - $CFG->filter_mediaplugin_enable_qt = 1; - $CFG->filter_mediaplugin_enable_wmp = 1; - $CFG->filter_mediaplugin_enable_rm = 1; + $CFG->core_media_enable_youtube = 1; + $CFG->core_media_enable_vimeo = 1; + $CFG->core_media_enable_mp3 = 1; + $CFG->core_media_enable_flv = 1; + $CFG->core_media_enable_swf = 1; + $CFG->core_media_enable_html5audio = 1; + $CFG->core_media_enable_html5video = 1; + $CFG->core_media_enable_qt = 1; + $CFG->core_media_enable_wmp = 1; + $CFG->core_media_enable_rm = 1; $filterplugin = new filter_mediaplugin(null, array()); diff --git a/filter/mediaplugin/version.php b/filter/mediaplugin/version.php index f5b2288882eb7..c10b57fa3e1bb 100644 --- a/filter/mediaplugin/version.php +++ b/filter/mediaplugin/version.php @@ -25,6 +25,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2011112900; // The current plugin version (Date: YYYYMMDDXX) -$plugin->requires = 2011112900; // Requires this Moodle version +$plugin->version = 2011121200; // The current plugin version (Date: YYYYMMDDXX) +$plugin->requires = 2011120500; // Requires this Moodle version $plugin->component = 'filter_mediaplugin'; // Full name of the plugin (used for diagnostics) diff --git a/lang/en/media.php b/lang/en/media.php new file mode 100644 index 0000000000000..15fd1756f34df --- /dev/null +++ b/lang/en/media.php @@ -0,0 +1,51 @@ +. + +/** + * Language strings for media embedding. + * @package core + * @subpackage media + * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$string['flashanimation'] = 'Flash animation'; +$string['flashanimation_desc'] = 'Files with extension *.swf. For security reasons this format is only embedded within trusted text.'; +$string['flashvideo'] = 'Flash video'; +$string['flashvideo_desc'] = 'Files with extension *.flv and *.f4v. Plays video clips using Flowplayer, requires Flash plugin and javascript.'; +$string['html5audio'] = 'HTML 5 audio'; +$string['html5audio_desc'] = 'Audio files with extension *.ogg, *.aac and *.mp3. Used primarily for mobile devices. (Format support depends on browser.)'; +$string['html5video'] = 'HTML 5 video'; +$string['html5video_desc'] = 'Video files with extension *.webm, *.m4v, *.ogv, *.mp4 and others. Used primarily for mobile devices. (Format support depends on browser.)'; +$string['mediaformats'] = 'Available players'; +$string['mediaformats_desc'] = 'When players are enabled in these settings, files can be embedded using the media filter (if enabled) or using a File or URL resources with the Embed option. When not enabled, these formats are not embedded and users can manually download or follow links to these resources. + +Where two players support the same format, enabling both increases compatibility across different devices such as mobile phones. It is possible to increase compatibility further by providing multiple files in different formats for a single audio or video clip.'; +$string['mediasettings'] = 'Media embedding'; +$string['mp3audio'] = 'MP3 audio'; +$string['mp3audio_desc'] = 'Files with extension *.mp3. Plays audio using Flowplayer, requires Flash plugin.'; +$string['legacyquicktime'] = 'QuickTime player'; +$string['legacyquicktime_desc'] = 'Files with extension *.mov, *.mp4, *.m4a, *.mp4 and *.mpg. Requires QuickTime player or codecs.'; +$string['legacyreal'] = 'Real media player'; +$string['legacyreal_desc'] = 'Files with extension *.rm, *.ra, *.ram, *.rp and *.rv. Requires RealPlayer.'; +$string['legacywmp'] = 'Windows media player'; +$string['legacywmp_desc'] = 'Files with extension *.avi and *.wmv. Fully compatible with Internet Explorer in Windows; may not work in other browsers or operating systems.'; +$string['legacyheading'] = 'Legacy media players'; +$string['legacyheading_desc'] = 'These players are not frequently used on the Web and require browser plugins that are less widely installed.'; +$string['sitevimeo'] = 'Vimeo'; +$string['sitevimeo_desc'] = 'Vimeo video sharing site.'; +$string['siteyoutube'] = 'YouTube'; +$string['siteyoutube_desc'] = 'YouTube video sharing site, video and playlist links supported.'; diff --git a/lib/medialib.php b/lib/medialib.php new file mode 100644 index 0000000000000..afde3b0b46082 --- /dev/null +++ b/lib/medialib.php @@ -0,0 +1,1232 @@ +. + +/** + * Classes for handling embedded media (mainly audio and video). + * + * These are used only from within the core media renderer. + * + * To embed media from Moodle code, do something like the following: + * + * $mediarenderer = $PAGE->get_renderer('core', 'media'); + * echo $mediarenderer->embed_url(new moodle_url('http://example.org/a.mp3')); + * + * You do not need to require this library file manually. Getting the renderer + * (the first line above) requires this library file automatically. + * + * @package core_media + * @copyright 2012 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +if (!defined('CORE_MEDIA_VIDEO_WIDTH')) { + /** + * Default video width if no width is specified; some players may do something + * more intelligent such as use real video width. + * + * May be defined in config.php if required. + */ + define('CORE_MEDIA_VIDEO_WIDTH', 400); +} +if (!defined('CORE_MEDIA_VIDEO_HEIGHT')) { + /** + * Default video height. May be defined in config.php if required. + */ + define('CORE_MEDIA_VIDEO_HEIGHT', 300); +} +if (!defined('CORE_MEDIA_AUDIO_WIDTH')) { + /** + * Default audio width if no width is specified. + * + * May be defined in config.php if required. + */ + define('CORE_MEDIA_AUDIO_WIDTH', 300); +} + + +/** + * Constants and static utility functions for use with core_media_renderer. + * + * @copyright 2011 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class core_media { + /** + * Option: Disable text link fallback. + * + * Use this option if you are going to print a visible link anyway so it is + * pointless to have one as fallback. + * + * To enable, set value to true. + */ + const OPTION_NO_LINK = 'nolink'; + + /** + * Option: When embedding, if there is no matching embed, do not use the + * default link fallback player; instead return blank. + * + * This is different from OPTION_NO_LINK because this option still uses the + * fallback link if there is some kind of embedding. Use this option if you + * are going to check if the return value is blank and handle it specially. + * + * To enable, set value to true. + */ + const OPTION_FALLBACK_TO_BLANK = 'embedorblank'; + + /** + * Option: Enable players which are only suitable for use when we trust the + * user who embedded the content. + * + * At present, this option enables the SWF player. + * + * To enable, set value to true. + */ + const OPTION_TRUSTED = 'trusted'; + + /** + * Option: Put a div around the output (if not blank) so that it displays + * as a block using the 'resourcecontent' CSS class. + * + * To enable, set value to true. + */ + const OPTION_BLOCK = 'block'; + + /** + * Given a string containing multiple URLs separated by #, this will split + * it into an array of moodle_url objects suitable for using when calling + * embed_alternatives. + * + * Note that the input string should NOT be html-escaped (i.e. if it comes + * from html, call html_entity_decode first). + * + * @param string $combinedurl String of 1 or more alternatives separated by # + * @param int $width Output variable: width (will be set to 0 if not specified) + * @param int $height Output variable: height (0 if not specified) + * @return array Array of 1 or more moodle_url objects + */ + public static function split_alternatives($combinedurl, &$width, &$height) { + $urls = explode('#', $combinedurl); + $width = 0; + $height = 0; + $returnurls = array(); + + foreach ($urls as $url) { + $matches = null; + + // You can specify the size as a separate part of the array like + // #d=640x480 without actually including a url in it. + if (preg_match('/^d=([\d]{1,4})x([\d]{1,4})$/i', $url, $matches)) { + $width = $matches[1]; + $height = $matches[2]; + continue; + } + + // Can also include the ?d= as part of one of the URLs (if you use + // more than one they will be ignored except the last). + if (preg_match('/\?d=([\d]{1,4})x([\d]{1,4})$/i', $url, $matches)) { + $width = $matches[1]; + $height = $matches[2]; + + // Trim from URL. + $url = str_replace($matches[0], '', $url); + } + + // Clean up url. + $url = clean_param($url, PARAM_URL); + if (empty($url)) { + continue; + } + + // Turn it into moodle_url object. + $returnurls[] = new moodle_url($url); + } + + return $returnurls; + } + + /** + * Returns the file extension for a URL. + * @param moodle_url $url URL + */ + public static function get_extension(moodle_url $url) { + // Note: Does not use textlib (. is UTF8-safe). + $filename = self::get_filename($url); + $dot = strrpos($filename, '.'); + if ($dot === false) { + return ''; + } else { + return strtolower(substr($filename, $dot + 1)); + } + } + + /** + * Obtains the filename from the moodle_url. + * @param moodle_url $url URL + * @return string Filename only (not escaped) + */ + public static function get_filename(moodle_url $url) { + $path = $url->get_path(); + // Remove everything before last / if present. Does not use textlib as / is UTF8-safe. + $slash = strrpos($path, '/'); + if ($slash !== false) { + $path = substr($path, $slash + 1); + } + + return $path; + } + + /** + * Guesses MIME type for a moodle_url based on file extension. + * @param moodle_url $url URL + * @return string MIME type + */ + public static function get_mimetype(moodle_url $url) { + return mimeinfo('type', self::get_filename($url)); + } +} + + +/** + * Base class for media players. + * + * Media players return embed HTML for a particular way of playing back audio + * or video (or another file type). + * + * In order to make the code more lightweight, this is not a plugin type + * (players cannot have their own settings, database tables, capabilities, etc). + * These classes are used only by core_media_renderer in outputrenderers.php. + * If you add a new class here (in core code) you must modify the + * get_players_raw function in that file to include it. + * + * If a Moodle installation wishes to add extra player objects they can do so + * by overriding that renderer in theme, and overriding the get_players_raw + * function. The new player class should then of course be defined within the + * custom theme or other suitable location, not in this file. + * + * @copyright 2011 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class core_media_player { + /** + * Placeholder text used to indicate where the fallback content is placed + * within a result. + */ + const PLACEHOLDER = ''; + + /** + * Generates code required to embed the player. + * + * The returned code contains a placeholder comment '' + * (constant core_media_player::PLACEHOLDER) which indicates the location + * where fallback content should be placed in the event that this type of + * player is not supported by user browser. + * + * The $urls parameter includes one or more alternative media formats that + * are supported by this player. It does not include formats that aren't + * supported (see list_supported_urls). + * + * The $options array contains key-value pairs. See OPTION_xx constants + * for documentation of standard option(s). + * + * @param array $urls URLs of media files + * @param string $name Display name; '' to use default + * @param int $width Optional width; 0 to use default + * @param int $height Optional height; 0 to use default + * @param array $options Options array + * @return string HTML code for embed + */ + public abstract function embed($urls, $name, $width, $height, $options); + + /** + * Gets the list of file extensions supported by this media player. + * + * Note: This is only required for the default implementation of + * list_supported_urls. If you override that function to determine + * supported URLs in some way other than by extension, then this function + * is not necessary. + * + * @return array Array of strings (extension not including dot e.g. 'mp3') + */ + public function get_supported_extensions() { + return array(); + } + + /** + * Lists keywords that must be included in a url that can be embedded with + * this player. Any such keywords should be added to the array. + * + * For example if this player supports FLV and F4V files then it should add + * '.flv' and '.f4v' to the array. (The check is not case-sensitive.) + * + * Default handling calls the get_supported_extensions function and adds + * a dot to each of those values, so players only need to override this + * if they don't implement get_supported_extensions. + * + * This is used to improve performance when matching links in the media filter. + * + * @return array Array of keywords to add to the embeddable markers list + */ + public function get_embeddable_markers() { + $markers = array(); + foreach ($this->get_supported_extensions() as $extension) { + $markers[] = '.' . $extension; + } + return $markers; + } + + /** + * Gets the ranking of this player. This is an integer used to decide which + * player to use (after applying other considerations such as which ones + * the user has disabled). + * + * Rank must be unique (no two players should have the same rank). + * + * Rank zero has a special meaning, indicating that this 'player' does not + * really embed the video. + * + * Rank is not a user-configurable value because it needs to be defined + * carefully in order to ensure that the embedding fallbacks actually work. + * It might be possible to have some user options which affect rank, but + * these would be best defined as e.g. checkboxes in settings that have + * a particular effect on the rank of a couple of plugins, rather than + * letting users generally alter rank. + * + * Note: Within medialib.php, players are listed in rank order (highest + * rank first). + * + * @return int Rank (higher is better) + */ + public abstract function get_rank(); + + /** + * @return bool True if player is enabled + */ + public function is_enabled() { + global $CFG; + + // With the class core_media_player_html5video it is enabled + // based on $CFG->core_media_enable_html5video. + $setting = str_replace('_player_', '_enable_', get_class($this)); + return !empty($CFG->{$setting}); + } + + /** + * Given a list of URLs, returns a reduced array containing only those URLs + * which are supported by this player. (Empty if none.) + * @param array $urls Array of moodle_url + * @param array $options Options (same as will be passed to embed) + * @return array Array of supported moodle_url + */ + public function list_supported_urls(array $urls, array $options = array()) { + $extensions = $this->get_supported_extensions(); + $result = array(); + foreach ($urls as $url) { + if (in_array(core_media::get_extension($url), $extensions)) { + $result[] = $url; + } + } + return $result; + } + + /** + * Obtains suitable name for media. Uses specified name if there is one, + * otherwise makes one up. + * @param string $name User-specified name ('' if none) + * @param array $urls Array of moodle_url used to make up name + * @return string Name + */ + protected function get_name($name, $urls) { + // If there is a specified name, use that. + if ($name) { + return $name; + } + + // Get filename of first URL. + $url = reset($urls); + $name = core_media::get_filename($url); + + // If there is more than one url, strip the extension as we could be + // referring to a different one or several at once. + if (count($urls) > 1) { + $name = preg_replace('~\.[^.]*$~', '', $name); + } + + return $name; + } + + /** + * Compares by rank order, highest first. Used for sort functions. + * @param core_media_player $a Player A + * @param core_media_player $b Player B + * @return int Negative if A should go before B, positive for vice versa + */ + public static function compare_by_rank(core_media_player $a, core_media_player $b) { + return $b->get_rank() - $a->get_rank(); + } + + /** + * Utility function that sets width and height to defaults if not specified + * as a parameter to the function (will be specified either if, (a) the calling + * code passed it, or (b) the URL included it). + * @param int $width Width passed to function (updated with final value) + * @param int $height Height passed to function (updated with final value) + */ + protected static function pick_video_size(&$width, &$height) { + if (!$width) { + $width = CORE_MEDIA_VIDEO_WIDTH; + $height = CORE_MEDIA_VIDEO_HEIGHT; + } + } +} + + +/** + * Base class for players which handle external links (YouTube etc). + * + * As opposed to media files. + * + * @copyright 2011 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class core_media_player_external extends core_media_player { + /** + * Array of matches from regular expression - subclass can assume these + * will be valid when the embed function is called, to save it rerunning + * the regex. + * @var array + */ + protected $matches; + + /** + * Part of a regular expression, including ending ~ symbol (note: these + * regexes use ~ instead of / because URLs and HTML code typically include + * / symbol and makes harder to read if you have to escape it). + * Matches the end part of a link after you have read the 'important' data + * including optional #d=400x300 at end of url, plus content of tag, + * up to . + * @var string + */ + const END_LINK_REGEX_PART = '[^#]*(#d=([\d]{1,4})x([\d]{1,4}))?~si'; + + public function embed($urls, $name, $width, $height, $options) { + return $this->embed_external(reset($urls), $name, $width, $height, $options); + } + + /** + * Obtains HTML code to embed the link. + * @param moodle_url $url Single URL to embed + * @param string $name Display name; '' to use default + * @param int $width Optional width; 0 to use default + * @param int $height Optional height; 0 to use default + * @param array $options Options array + * @return string HTML code for embed + */ + protected abstract function embed_external(moodle_url $url, $name, $width, $height, $options); + + public function list_supported_urls(array $urls, array $options = array()) { + // These only work with a SINGLE url (there is no fallback). + if (count($urls) != 1) { + return array(); + } + $url = reset($urls); + + // Check against regex. + if (preg_match($this->get_regex(), $url->out(false), $this->matches)) { + return array($url); + } + + return array(); + } + + /** + * Returns regular expression used to match URLs that this player handles + * @return string PHP regular expression e.g. '~^https?://example.org/~' + */ + protected function get_regex() { + return '~^unsupported~'; + } + + /** + * Annoyingly, preg_match $matches result does not always have the same + * number of parameters - it leaves out optional ones at the end. WHAT. + * Anyway, this function can be used to fix it. + * @param array $matches Array that should be adjusted + * @param int $count Number of capturing groups (=6 to make $matches[6] work) + */ + protected static function fix_match_count(&$matches, $count) { + for ($i = count($matches); $i <= $count; $i++) { + $matches[$i] = false; + } + } +} + + +/** + * Player that embeds Vimeo links. + * + * @copyright 2011 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_media_player_vimeo extends core_media_player_external { + protected function embed_external(moodle_url $url, $name, $width, $height, $options) { + $videoid = $this->matches[1]; + $info = s($name); + + // Note: resizing via url is not supported, user can click the fullscreen + // button instead. iframe embedding is not xhtml strict but it is the only + // option that seems to work on most devices. + self::pick_video_size($width, $height); + + $output = << + + +OET; + + return $output; + } + + protected function get_regex() { + // Initial part of link. + $start = '~^http://vimeo\.com/'; + // Middle bit: either watch?v= or v/. + $middle = '([0-9]+)'; + return $start . $middle . core_media_player_external::END_LINK_REGEX_PART; + } + + public function get_rank() { + return 1010; + } + + public function get_embeddable_markers() { + return array('vimeo.com/'); + } +} + +/** + * Player that creates YouTube embedding. + * + * @copyright 2011 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_media_player_youtube extends core_media_player_external { + protected function embed_external(moodle_url $url, $name, $width, $height, $options) { + global $CFG; + + $site = $this->matches[1]; + $videoid = $this->matches[3]; + + $info = trim($name); + if (empty($info) or strpos($info, 'http') === 0) { + $info = get_string('siteyoutube', 'core_media'); + } + $info = s($info); + + self::pick_video_size($width, $height); + + if (empty($CFG->xmlstrictheaders)) { + return << +OET; + } + + // NOTE: we can not use any link fallback because it breaks built-in + // player on iOS devices. + $output = << + + + + + + +OET; + + return $output; + } + + protected function get_regex() { + // Initial part of link. + $start = '~^(https?://www\.youtube(-nocookie)?\.com)/'; + // Middle bit: either watch?v= or v/. + $middle = '(?:watch\?v=|v/)([a-z0-9\-_]+)'; + return $start . $middle . core_media_player_external::END_LINK_REGEX_PART; + } + + public function get_rank() { + // I decided to make the link-embedding ones (that don't handle file + // formats) have ranking in the 1000 range. + return 1001; + } + + public function get_embeddable_markers() { + return array('youtube'); + } +} + + +/** + * Player that creates YouTube playlist embedding. + * + * @copyright 2011 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_media_player_youtube_playlist extends core_media_player_external { + public function is_enabled() { + global $CFG; + // Use the youtube on/off flag. + return $CFG->core_media_enable_youtube; + } + + protected function embed_external(moodle_url $url, $name, $width, $height, $options) { + $site = $this->matches[1]; + $playlist = $this->matches[3]; + + $info = trim($name); + if (empty($info) or strpos($info, 'http') === 0) { + $info = get_string('siteyoutube', 'core_media'); + } + $info = s($info); + + self::pick_video_size($width, $height); + + // TODO: iframe HTML 5 video not implemented and object does not work + // on iOS devices. + $fallback = core_media_player::PLACEHOLDER; + $output = << + + + + +$fallback + +OET; + + return $output; + } + + protected function get_regex() { + // Initial part of link. + $start = '~^(https?://www\.youtube(-nocookie)?\.com)/'; + // Middle bit: either view_play_list?p= or p/ (doesn't work on youtube) or playlist?list=. + $middle = '(?:view_play_list\?p=|p/|playlist\?list=)([a-z0-9\-_]+)'; + return $start . $middle . core_media_player_external::END_LINK_REGEX_PART; + } + + public function get_rank() { + // I decided to make the link-embedding ones (that don't handle file + // formats) have ranking in the 1000 range. + return 1000; + } + + public function get_embeddable_markers() { + return array('youtube'); + } +} + + +/** + * MP3 player inserted using JavaScript. + * + * @copyright 2011 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_media_player_mp3 extends core_media_player { + public function embed($urls, $name, $width, $height, $options) { + // Use first url (there can actually be only one unless some idiot + // enters two mp3 files as alternatives). + $url = reset($urls); + + // Unique id even across different http requests made at the same time + // (for AJAX, iframes). + $id = 'core_media_mp3_' . md5(time() . '_' . rand()); + + // When Flash or JavaScript are not available only the fallback is displayed, + // using span not div because players are inline elements. + $spanparams = array('id' => $id, 'class' => 'mediaplugin mediaplugin_mp3'); + if ($width) { + $spanparams['style'] = 'width: ' . $width . 'px'; + } + $output = html_writer::tag('span', core_media_player::PLACEHOLDER, $spanparams); + // We can not use standard JS init because this may be cached + // note: use 'small' size unless embedding in block mode. + $output .= html_writer::script(js_writer::function_call( + 'M.util.add_audio_player', array($id, $url->out(false), + empty($options[core_media::OPTION_BLOCK])))); + + return $output; + } + + public function get_supported_extensions() { + return array('mp3'); + } + + public function get_rank() { + return 80; + } +} + + +/** + * Flash video player inserted using JavaScript. + * + * @copyright 2011 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_media_player_flv extends core_media_player { + public function embed($urls, $name, $width, $height, $options) { + // Use first url (there can actually be only one unless some idiot + // enters two mp3 files as alternatives). + $url = reset($urls); + + // Unique id even across different http requests made at the same time + // (for AJAX, iframes). + $id = 'core_media_flv_' . md5(time() . '_' . rand()); + + // Compute width and height. + $autosize = false; + if (!$width && !$height) { + $width = CORE_MEDIA_VIDEO_WIDTH; + $height = CORE_MEDIA_VIDEO_HEIGHT; + $autosize = true; + } + + // Fallback span (will normally contain link). + $output = html_writer::tag('span', core_media_player::PLACEHOLDER, + array('id'=>$id, 'class'=>'mediaplugin mediaplugin_flv')); + // We can not use standard JS init because this may be cached. + $output .= html_writer::script(js_writer::function_call( + 'M.util.add_video_player', array($id, addslashes_js($url->out(false)), + $width, $height, $autosize))); + return $output; + } + + public function get_supported_extensions() { + return array('flv', 'f4v'); + } + + public function get_rank() { + return 70; + } +} + + +/** + * Embeds Windows Media Player using object tag. + * + * @copyright 2011 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_media_player_wmp extends core_media_player { + public function embed($urls, $name, $width, $height, $options) { + // Get URL (we just use first, probably there is only one). + $firsturl = reset($urls); + $url = $firsturl->out(false); + + // Work out width. + if (!$width || !$height) { + // Object tag has default size. + $mpsize = ''; + $size = 'width="' . CORE_MEDIA_VIDEO_WIDTH . + '" height="' . (CORE_MEDIA_VIDEO_HEIGHT+64) . '"'; + $autosize = 'true'; + } else { + $size = 'width="' . $width . '" height="' . ($height + 15) . '"'; + $mpsize = 'width="' . $width . '" height="' . ($height + 64) . '"'; + $autosize = 'false'; + } + + // MIME type for object tag. + $mimetype = core_media::get_mimetype($firsturl); + + $fallback = core_media_player::PLACEHOLDER; + + // Embed code. + return << + + + + + + + + + + + + + + + + + + + + + + + $fallback + + + + + +OET; + } + + public function get_supported_extensions() { + return array('wmv', 'avi'); + } + + public function get_rank() { + return 60; + } +} + + +/** + * Media player using object tag and QuickTime player. + * + * @copyright 2011 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_media_player_qt extends core_media_player { + public function embed($urls, $name, $width, $height, $options) { + // Show first URL. + $firsturl = reset($urls); + $url = $firsturl->out(true); + + // Work out size. + if (!$width || !$height) { + $size = 'width="' . CORE_MEDIA_VIDEO_WIDTH . + '" height="' . (CORE_MEDIA_VIDEO_HEIGHT + 15) . '"'; + } else { + $size = 'width="' . $width . '" height="' . ($height + 15) . '"'; + } + + // MIME type for object tag. + $mimetype = core_media::get_mimetype($firsturl); + + $fallback = core_media_player::PLACEHOLDER; + + // Embed code. + return << + + + + + + + + + + + + + + + + + + + $fallback + + + + + +OET; + } + + public function get_supported_extensions() { + return array('mpg', 'mpeg', 'mov', 'mp4', 'm4v', 'm4a'); + } + + public function get_rank() { + return 50; + } +} + + +/** + * Media player using object tag and RealPlayer. + * + * Hopefully nobody is using this obsolete format any more! + * + * @copyright 2011 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_media_player_rm extends core_media_player { + public function embed($urls, $name, $width, $height, $options) { + // Show first URL. + $firsturl = reset($urls); + $url = $firsturl->out(true); + + // Get name to use as title. + $info = s($this->get_name($name, $urls)); + + // The previous version of this code has the following comment, which + // I don't understand, but trust it is correct: + // Note: the size is hardcoded intentionally because this does not work anyway! + $width = CORE_MEDIA_VIDEO_WIDTH; + $height = CORE_MEDIA_VIDEO_HEIGHT; + + $fallback = core_media_player::PLACEHOLDER; + return << + + + + + + + + + $fallback + + + + + +OET; + } + + public function get_supported_extensions() { + return array('ra', 'ram', 'rm', 'rv'); + } + + public function get_rank() { + return 40; + } +} + + +/** + * Media player for Flash SWF files. + * + * This player contains additional security restriction: it will only be used + * if you add option core_media_player_swf::ALLOW = true. + * + * Code should only set this option if it has verified that the data was + * embedded by a trusted user (e.g. in trust text). + * + * @copyright 2011 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_media_player_swf extends core_media_player { + public function embed($urls, $name, $width, $height, $options) { + self::pick_video_size($width, $height); + + $firsturl = reset($urls); + $url = $firsturl->out(true); + + $fallback = core_media_player::PLACEHOLDER; + $output = << + + + + + + + + + + + + + + + + + +$fallback + + + + + +OET; + + return $output; + } + + public function get_supported_extensions() { + return array('swf'); + } + + public function list_supported_urls(array $urls, array $options = array()) { + // Not supported unless the creator is trusted. + if (empty($options[core_media::OPTION_TRUSTED])) { + return array(); + } + return parent::list_supported_urls($urls, $options); + } + + public function get_rank() { + return 30; + } +} + + +/** + * Player that creates HTML5 '; + $link = 'mediafallbacklink'; + + $this->assertEquals($hasqt, self::str_contains($text, $qt)); + $this->assertEquals($hashtml5, self::str_contains($text, $html5)); + $this->assertEquals($haslink, self::str_contains($text, $link)); + } + + /** + * Test for core_media_renderer embed_url. + * Check SWF works including the special option required to enable it + */ + public function test_embed_url_swf() { + global $CFG, $PAGE; + $CFG->core_media_enable_swf = true; + $renderer = new core_media_renderer_test($PAGE, ''); + + // Without any options... + $url = new moodle_url('http://example.org/test.swf'); + $t = $renderer->embed_url($url); + $this->assertFalse(self::str_contains($t, '')); + + // ...and with the 'no it's safe, I checked it' option. + $url = new moodle_url('http://example.org/test.swf'); + $t = $renderer->embed_url($url, '', 0, 0, array(core_media::OPTION_TRUSTED => true)); + $this->assertTrue(self::str_contains($t, '')); + } + + /** + * Test for core_media_renderer embed_url. + * Exercises all the basic formats not covered elsewhere. + */ + public function test_embed_url_other_formats() { + global $CFG, $PAGE; + + // Enable all players and get renderer. + $CFG->core_media_enable_html5audio = true; + $CFG->core_media_enable_mp3 = true; + $CFG->core_media_enable_flv = true; + $CFG->core_media_enable_wmp = true; + $CFG->core_media_enable_rm = true; + $CFG->core_media_enable_youtube = true; + $CFG->core_media_enable_vimeo = true; + $renderer = new core_media_renderer_test($PAGE, ''); + + // Check each format one at a time. This is a basic check to be sure + // the HTML is included for files of the right type, not a test that + // the HTML itself is correct. + + // Format: mp3. + $url = new moodle_url('http://example.org/test.mp3'); + $t = $renderer->embed_url($url); + $this->assertTrue(self::str_contains($t, 'core_media_mp3_')); + + // Format: flv. + $url = new moodle_url('http://example.org/test.flv'); + $t = $renderer->embed_url($url); + $this->assertTrue(self::str_contains($t, 'core_media_flv_')); + + // Format: wmp. + $url = new moodle_url('http://example.org/test.avi'); + $t = $renderer->embed_url($url); + $this->assertTrue(self::str_contains($t, '6BF52A52-394A-11d3-B153-00C04F79FAA6')); + + // Format: rm. + $url = new moodle_url('http://example.org/test.rm'); + $t = $renderer->embed_url($url); + $this->assertTrue(self::str_contains($t, 'CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA')); + + // Format: youtube. + $url = new moodle_url('http://www.youtube.com/watch?v=vyrwMmsufJc'); + $t = $renderer->embed_url($url); + $this->assertTrue(self::str_contains($t, '')); + $url = new moodle_url('http://www.youtube.com/v/vyrwMmsufJc'); + $t = $renderer->embed_url($url); + $this->assertTrue(self::str_contains($t, '')); + + // Format: youtube playlist. + $url = new moodle_url('http://www.youtube.com/view_play_list?p=PL6E18E2927047B662'); + $t = $renderer->embed_url($url); + $this->assertTrue(self::str_contains($t, '')); + $url = new moodle_url('http://www.youtube.com/playlist?list=PL6E18E2927047B662'); + $t = $renderer->embed_url($url); + $this->assertTrue(self::str_contains($t, '')); + $url = new moodle_url('http://www.youtube.com/p/PL6E18E2927047B662'); + $t = $renderer->embed_url($url); + $this->assertTrue(self::str_contains($t, '')); + + // Format: vimeo. + $url = new moodle_url('http://vimeo.com/1176321'); + $t = $renderer->embed_url($url); + $this->assertTrue(self::str_contains($t, '')); + + // Format: html5audio. + $this->pretend_to_be_firefox(); + $url = new moodle_url('http://example.org/test.ogg'); + $t = $renderer->embed_url($url); + $this->assertTrue(self::str_contains($t, '')); + } + + /** + * Test for core_media_renderer embed_url. + * Checks the EMBED_OR_BLANK option. + */ + public function test_embed_or_blank() { + global $CFG, $PAGE; + $CFG->core_media_enable_html5audio = true; + $this->pretend_to_be_firefox(); + + $renderer = new core_media_renderer_test($PAGE, ''); + + $options = array(core_media::OPTION_FALLBACK_TO_BLANK => true); + + // Embed that does match something should still include the link too. + $url = new moodle_url('http://example.org/test.ogg'); + $t = $renderer->embed_url($url, '', 0, 0, $options); + $this->assertTrue(self::str_contains($t, '')); + $this->assertTrue(self::str_contains($t, 'mediafallbacklink')); + + // Embed that doesn't match something should be totally blank. + $url = new moodle_url('http://example.org/test.mp4'); + $t = $renderer->embed_url($url, '', 0, 0, $options); + $this->assertEquals('', $t); + } + + /** + * Test for core_media_renderer embed_url. + * Checks that size is passed through correctly to player objects and tests + * size support in html5video output. + */ + public function test_embed_url_size() { + global $CFG, $PAGE; + + // Technically this could break in every format and they handle size + // in several different ways, but I'm too lazy to test it in every + // format, so let's just pick one to check the values get passed + // through. + $CFG->core_media_enable_html5video = true; + $renderer = new core_media_renderer_test($PAGE, ''); + $url = new moodle_url('http://example.org/test.mp4'); + + // HTML5 default size - specifies core width and does not specify height. + $t = $renderer->embed_url($url); + $this->assertTrue(self::str_contains($t, 'width="' . CORE_MEDIA_VIDEO_WIDTH . '"')); + $this->assertFalse(self::str_contains($t, 'height')); + + // HTML5 specified size - specifies both. + $t = $renderer->embed_url($url, '', '666', '101'); + $this->assertTrue(self::str_contains($t, 'width="666"')); + $this->assertTrue(self::str_contains($t, 'height="101"')); + + // HTML5 size specified in url, overrides call. + $url = new moodle_url('http://example.org/test.mp4?d=123x456'); + $t = $renderer->embed_url($url, '', '666', '101'); + $this->assertTrue(self::str_contains($t, 'width="123"')); + $this->assertTrue(self::str_contains($t, 'height="456"')); + } + + /** + * Test for core_media_renderer embed_url. + * Checks that name is passed through correctly to player objects and tests + * name support in html5video output. + */ + public function test_embed_url_name() { + global $CFG, $PAGE; + + // As for size this could break in every format but I'm only testing + // html5video. + $CFG->core_media_enable_html5video = true; + $renderer = new core_media_renderer_test($PAGE, ''); + $url = new moodle_url('http://example.org/test.mp4'); + + // HTML5 default name - use filename. + $t = $renderer->embed_url($url); + $this->assertTrue(self::str_contains($t, 'title="test.mp4"')); + + // HTML5 specified name - check escaping. + $t = $renderer->embed_url($url, 'frog & toad'); + $this->assertTrue(self::str_contains($t, 'title="frog & toad"')); + } + + /** + * Test for core_media_renderer split_alternatives. + */ + public function test_split_alternatives() { + // Single URL - identical moodle_url. + $mp4 = 'http://example.org/test.mp4'; + $result = core_media::split_alternatives($mp4, $w, $h); + $this->assertEquals($mp4, $result[0]->out(false)); + + // Width and height weren't specified. + $this->assertEquals(0, $w); + $this->assertEquals(0, $h); + + // Two URLs - identical moodle_urls. + $webm = 'http://example.org/test.webm'; + $result = core_media::split_alternatives("$mp4#$webm", $w, $h); + $this->assertEquals($mp4, $result[0]->out(false)); + $this->assertEquals($webm, $result[1]->out(false)); + + // Two URLs plus dimensions. + $size = 'd=400x280'; + $result = core_media::split_alternatives("$mp4#$webm#$size", $w, $h); + $this->assertEquals($mp4, $result[0]->out(false)); + $this->assertEquals($webm, $result[1]->out(false)); + $this->assertEquals(400, $w); + $this->assertEquals(280, $h); + + // Two URLs plus legacy dimensions (use last one). + $result = core_media::split_alternatives("$mp4?d=1x1#$webm?$size", $w, $h); + $this->assertEquals($mp4, $result[0]->out(false)); + $this->assertEquals($webm, $result[1]->out(false)); + $this->assertEquals(400, $w); + $this->assertEquals(280, $h); + } + + /** + * Test for core_media_renderer embed_alternatives (with multiple urls) + */ + public function test_embed_alternatives() { + global $PAGE, $CFG; + + // Most aspects of this are same as single player so let's just try + // a single typical / complicated scenario. + + // MP3, WebM and FLV. + $urls = array( + new moodle_url('http://example.org/test.mp4'), + new moodle_url('http://example.org/test.webm'), + new moodle_url('http://example.org/test.flv'), + ); + + // Enable html5 and flv. + $CFG->core_media_enable_html5video = true; + $CFG->core_media_enable_flv = true; + $renderer = new core_media_renderer_test($PAGE, ''); + + // Result should contain HTML5 with two sources + FLV. + $t = $renderer->embed_alternatives($urls); + + // HTML5 sources - mp4, not flv or webm (not supported in Safari). + $this->assertTrue(self::str_contains($t, 'assertFalse(self::str_contains($t, 'assertFalse(self::str_contains($t, 'assertTrue((bool)preg_match('~core_media_flv_.*pretend_to_be_firefox(); + $t = $renderer->embed_alternatives($urls); + + // HTML5 sources - webm, not not flv or mp4 (not supported in Firefox). + $this->assertFalse(self::str_contains($t, 'assertTrue(self::str_contains($t, 'assertFalse(self::str_contains($t, 'out(false); + } + return implode(',', $out); + } + + /** + * Converts associative array into a semicolon-separated string for easier + * testing. + * + * @param array $options Associative array + * @return string String of form 'a=b;c=d' + */ + public static function string_options($options) { + $out = ''; + foreach ($options as $key => $value) { + if ($out) { + $out .= ';'; + } + $out .= "$key=$value"; + } + return $out; + } +} + +/** + * Media player stub for testing purposes. + */ +class core_media_player_test extends core_media_player { + /** @var array Array of supported extensions */ + public $ext; + /** @var int Player rank */ + public $rank; + /** @var int Arbitrary number */ + public $num; + + /** + * @param int $num Number (used in output) + * @param int $rank Player rank + * @param array $ext Array of supported extensions + */ + public function __construct($num = 1, $rank = 13, $ext = array('tst', 'test')) { + $this->ext = $ext; + $this->rank = $rank; + $this->num = $num; + } + + public function embed($urls, $name, $width, $height, $options) { + return $this->num . ':' . medialib_test::string_urls($urls) . + ",$name,$width,$height,," . medialib_test::string_options($options); + } + + public function get_supported_extensions() { + return $this->ext; + } + + public function get_rank() { + return $this->rank; + } +} + +/** + * Media renderer override for testing purposes. + */ +class core_media_renderer_test extends core_media_renderer { + /** + * Access list of players as string, shortening it by getting rid of + * repeated text. + * @return string Comma-separated list of players + */ + public function get_players_test() { + $players = $this->get_players(); + $out = ''; + foreach ($players as $player) { + if ($out) { + $out .= ', '; + } + $out .= str_replace('core_media_player_', '', get_class($player)); + } + return $out; + } +} diff --git a/lib/upgrade.txt b/lib/upgrade.txt index ffe792af5f194..8bd6316bca9ab 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -12,7 +12,9 @@ Note: debug messages and will produce fatal errors API changes: + * send_stored_file() has changed its interface +* deleted several resourcelib_embed_* functions from resourcelib.php === 2.2 === diff --git a/mod/upgrade.txt b/mod/upgrade.txt index 3419e8200a743..20957dcd863bc 100644 --- a/mod/upgrade.txt +++ b/mod/upgrade.txt @@ -5,12 +5,16 @@ information provided here is intended especially for developers. === 2.3 === required changes in code: + * define the capability mod/xxx:addinstance (and the corresponding lang string) (unless your mod is a MOD_ARCHETYPE_SYSTEM). * xxx_pluginfile() is now given the 7th parameter (hopefully the last one) that contains additional options for the file serving. The array should be re-passed to send_stored_file(). +* most resourcelib_embed_* functions are replaced with core_media_renderer; + for an example, see mod/resource/locallib.php, resource_display_embed() + === 2.2 === diff --git a/theme/base/style/core.css b/theme/base/style/core.css index 2857512abd9b3..6eb8fa3dddc63 100644 --- a/theme/base/style/core.css +++ b/theme/base/style/core.css @@ -765,8 +765,13 @@ body.tag .managelink {padding: 5px;} .dir-rtl .felement.feditor select {margin-right:18.75%;margin-left:auto;} .dir-rtl .mform .fitem .felement {margin-right: 16%;margin-left:auto;} -/* Resourcelib mp3 player size: only width could be changed here, height hardcoded in JS */ -.resourcecontent .resourcemediaplugin_mp3 object {height:25px; width: 600px} +/* Audio player size in 'block' mode (can only change width, height is hardcoded in JS) */ +.resourcecontent .mediaplugin_mp3 object {height:25px; width: 600px} +.resourcecontent audio.mediaplugin_html5audio {width: 600px} + +/* Audio player size in 'inline' mode (can only change width, as above) */ +.mediaplugin_mp3 object {height:15px;width:300px} +audio.mediaplugin_html5audio {width: 300px} /* Fix for SubScript & SuperScript