MDL-33116 Media filter broken with slasharguments off
[moodle.git] / lib / tests / medialib_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Test classes for handling embedded media (audio/video).
19  *
20  * @package core_media
21  * @copyright 2012 The Open University
22  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 defined('MOODLE_INTERNAL') || die();
27 global $CFG;
28 require_once($CFG->libdir . '/medialib.php');
30 /**
31  * Test script for media embedding.
32  */
33 class medialib_testcase extends advanced_testcase {
35     /** @var array Files covered by test */
36     public static $includecoverage = array('lib/medialib.php', 'lib/outputrenderers.php');
38     /**
39      * Pre-test setup. Preserves $CFG.
40      */
41     public function setUp() {
42         global $CFG;
43         parent::setUp();
45         // Reset $CFG and $SERVER.
46         $this->resetAfterTest(true);
48         // Consistent initial setup: all players disabled.
49         $CFG->core_media_enable_html5video = false;
50         $CFG->core_media_enable_html5audio = false;
51         $CFG->core_media_enable_mp3 = false;
52         $CFG->core_media_enable_flv = false;
53         $CFG->core_media_enable_wmp = false;
54         $CFG->core_media_enable_qt = false;
55         $CFG->core_media_enable_rm = false;
56         $CFG->core_media_enable_youtube = false;
57         $CFG->core_media_enable_vimeo = false;
58         $CFG->core_media_enable_swf = false;
60         // Strict headers turned off.
61         $CFG->xmlstrictheaders = false;
63         $_SERVER = array('HTTP_USER_AGENT' => '');
64         $this->pretend_to_be_safari();
65     }
67     /**
68      * Sets user agent to Safari.
69      */
70     private function pretend_to_be_safari() {
71         // Pretend to be using Safari browser (must support mp4 for tests to work).
72         $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) ' .
73                 'AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1';
74     }
76     /**
77      * Sets user agent to Firefox.
78      */
79     private function pretend_to_be_firefox() {
80         // Pretend to be using Firefox browser (must support ogg for tests to work).
81         $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 6.1; rv:8.0) Gecko/20100101 Firefox/8.0';
82     }
84     /**
85      * Test for the core_media_player is_enabled.
86      */
87     public function test_is_enabled() {
88         global $CFG;
90         // Test enabled: unset, 0, 1.
91         $test = new core_media_player_test;
92         $this->assertFalse($test->is_enabled());
93         $CFG->core_media_enable_test = 0;
94         $this->assertFalse($test->is_enabled());
95         $CFG->core_media_enable_test = 1;
96         $this->assertTrue($test->is_enabled());
97     }
99     /**
100      * Test for core_media::get_filename.
101      */
102     public function test_get_filename() {
103         $this->assertEquals('frog.mp4', core_media::get_filename(new moodle_url(
104                 '/pluginfile.php/312/mod_page/content/7/frog.mp4')));
105         // This should work even though slasharguments is true, because we want
106         // it to support 'legacy' links if somebody toggles the option later.
107         $this->assertEquals('frog.mp4', core_media::get_filename(new moodle_url(
108                 '/pluginfile.php?file=/312/mod_page/content/7/frog.mp4')));
109     }
111     /**
112      * Test for core_media::get_extension.
113      */
114     public function test_get_extension() {
115         $this->assertEquals('mp4', core_media::get_extension(new moodle_url(
116                 '/pluginfile.php/312/mod_page/content/7/frog.mp4')));
117         $this->assertEquals('', core_media::get_extension(new moodle_url(
118                 '/pluginfile.php/312/mod_page/content/7/frog')));
119         $this->assertEquals('mp4', core_media::get_extension(new moodle_url(
120                 '/pluginfile.php?file=/312/mod_page/content/7/frog.mp4')));
121         $this->assertEquals('', core_media::get_extension(new moodle_url(
122                 '/pluginfile.php?file=/312/mod_page/content/7/frog')));
123     }
125     /**
126      * Test for the core_media_player list_supported_urls.
127      */
128     public function test_list_supported_urls() {
129         global $CFG;
130         $test = new core_media_player_test;
132         // Some example URLs.
133         $supported1 = new moodle_url('http://example.org/1.test');
134         $supported2 = new moodle_url('http://example.org/2.TST');
135         $unsupported = new moodle_url('http://example.org/2.jpg');
137         // No URLs => none.
138         $result = $test->list_supported_urls(array());
139         $this->assertEquals(array(), $result);
141         // One supported URL => same.
142         $result = $test->list_supported_urls(array($supported1));
143         $this->assertEquals(array($supported1), $result);
145         // Two supported URLS => same.
146         $result = $test->list_supported_urls(array($supported1, $supported2));
147         $this->assertEquals(array($supported1, $supported2), $result);
149         // One unsupported => none.
150         $result = $test->list_supported_urls(array($unsupported));
151         $this->assertEquals(array(), $result);
153         // Two supported and one unsupported => same.
154         $result = $test->list_supported_urls(array($supported2, $unsupported, $supported1));
155         $this->assertEquals(array($supported2, $supported1), $result);
156     }
158     /**
159      * Test for core_media_renderer get_players
160      */
161     public function test_get_players() {
162         global $CFG, $PAGE;
164         // All players are initially disabled (except link, which you can't).
165         $renderer = new core_media_renderer_test($PAGE, '');
166         $this->assertEquals('link', $renderer->get_players_test());
168         // A couple enabled, check the order.
169         $CFG->core_media_enable_html5audio = true;
170         $CFG->core_media_enable_mp3 = true;
171         $renderer = new core_media_renderer_test($PAGE, '');
172         $this->assertEquals('mp3, html5audio, link', $renderer->get_players_test());
173     }
175     /**
176      * Test for core_media_renderer can_embed_url
177      */
178     public function test_can_embed_url() {
179         global $CFG, $PAGE;
181         // All players are initially disabled, so mp4 cannot be rendered.
182         $url = new moodle_url('http://example.org/test.mp4');
183         $renderer = new core_media_renderer_test($PAGE, '');
184         $this->assertFalse($renderer->can_embed_url($url));
186         // Enable QT player.
187         $CFG->core_media_enable_qt = true;
188         $renderer = new core_media_renderer_test($PAGE, '');
189         $this->assertTrue($renderer->can_embed_url($url));
191         // QT + html5.
192         $CFG->core_media_enable_html5video = true;
193         $renderer = new core_media_renderer_test($PAGE, '');
194         $this->assertTrue($renderer->can_embed_url($url));
196         // Only html5.
197         $CFG->core_media_enable_qt = false;
198         $renderer = new core_media_renderer_test($PAGE, '');
199         $this->assertTrue($renderer->can_embed_url($url));
201         // Only WMP.
202         $CFG->core_media_enable_html5video = false;
203         $CFG->core_media_enable_wmp = true;
204         $renderer = new core_media_renderer_test($PAGE, '');
205         $this->assertFalse($renderer->can_embed_url($url));
206     }
208     /**
209      * Test for core_media_renderer embed_url.
210      * Checks multiple format/fallback support.
211      */
212     public function test_embed_url_fallbacks() {
213         global $CFG, $PAGE;
215         $url = new moodle_url('http://example.org/test.mp4');
217         // All plugins disabled, NOLINK option.
218         $renderer = new core_media_renderer_test($PAGE, '');
219         $t = $renderer->embed_url($url, 0, 0, '',
220                 array(core_media::OPTION_NO_LINK => true));
221         // Completely empty.
222         $this->assertEquals('', $t);
224         // All plugins disabled but not NOLINK.
225         $renderer = new core_media_renderer_test($PAGE, '');
226         $t = $renderer->embed_url($url);
227         $this->assert_contents(false, false, true, $t);
229         // HTML5 plugin enabled.
230         $CFG->core_media_enable_html5video = true;
231         $renderer = new core_media_renderer_test($PAGE, '');
232         $t = $renderer->embed_url($url);
233         $this->assert_contents(false, true, true, $t);
235         // QT plugin enabled as well.
236         $CFG->core_media_enable_qt = true;
237         $renderer = new core_media_renderer_test($PAGE, '');
238         $t = $renderer->embed_url($url);
239         $this->assert_contents(true, true, true, $t);
241         // Nolink turned off, plugins still enabled.
242         $t = $renderer->embed_url($url, 0, 0, '',
243                 array(core_media::OPTION_NO_LINK => true));
244         $this->assert_contents(true, true, false, $t);
245     }
247     /**
248      * Checks the contents of the resulting HTML code to ensure it used the
249      * correct embed method(s).
250      *
251      * @param bool $hasqt True if it should have QT embed code
252      * @param bool $hashtml5 True if it should have HTML5 embed code
253      * @param bool $haslink True if it should have a fallback link
254      * @param string $text HTML content
255      */
256     private function assert_contents($hasqt, $hashtml5, $haslink, $text) {
257         // I tried to avoid making it specific to the exact details of the html
258         // code, picking out only single key strings that would let it check
259         // whether final code contains the right things.
260         $qt = 'qtplugin.cab';
261         $html5 = '</video>';
262         $link = 'mediafallbacklink';
264         $this->assertEquals($hasqt, self::str_contains($text, $qt));
265         $this->assertEquals($hashtml5, self::str_contains($text, $html5));
266         $this->assertEquals($haslink, self::str_contains($text, $link));
267     }
269     /**
270      * Test for core_media_renderer embed_url.
271      * Check SWF works including the special option required to enable it
272      */
273     public function test_embed_url_swf() {
274         global $CFG, $PAGE;
275         $CFG->core_media_enable_swf = true;
276         $renderer = new core_media_renderer_test($PAGE, '');
278         // Without any options...
279         $url = new moodle_url('http://example.org/test.swf');
280         $t = $renderer->embed_url($url);
281         $this->assertFalse(self::str_contains($t, '</object>'));
283         // ...and with the 'no it's safe, I checked it' option.
284         $url = new moodle_url('http://example.org/test.swf');
285         $t = $renderer->embed_url($url, '', 0, 0, array(core_media::OPTION_TRUSTED => true));
286         $this->assertTrue(self::str_contains($t, '</object>'));
287     }
289     /**
290      * Test for core_media_renderer embed_url.
291      * Exercises all the basic formats not covered elsewhere.
292      */
293     public function test_embed_url_other_formats() {
294         global $CFG, $PAGE;
296         // Enable all players and get renderer.
297         $CFG->core_media_enable_html5audio = true;
298         $CFG->core_media_enable_mp3 = true;
299         $CFG->core_media_enable_flv = true;
300         $CFG->core_media_enable_wmp = true;
301         $CFG->core_media_enable_rm = true;
302         $CFG->core_media_enable_youtube = true;
303         $CFG->core_media_enable_vimeo = true;
304         $renderer = new core_media_renderer_test($PAGE, '');
306         // Check each format one at a time. This is a basic check to be sure
307         // the HTML is included for files of the right type, not a test that
308         // the HTML itself is correct.
310         // Format: mp3.
311         $url = new moodle_url('http://example.org/test.mp3');
312         $t = $renderer->embed_url($url);
313         $this->assertTrue(self::str_contains($t, 'core_media_mp3_'));
315         // Format: flv.
316         $url = new moodle_url('http://example.org/test.flv');
317         $t = $renderer->embed_url($url);
318         $this->assertTrue(self::str_contains($t, 'core_media_flv_'));
320         // Format: wmp.
321         $url = new moodle_url('http://example.org/test.avi');
322         $t = $renderer->embed_url($url);
323         $this->assertTrue(self::str_contains($t, '6BF52A52-394A-11d3-B153-00C04F79FAA6'));
325         // Format: rm.
326         $url = new moodle_url('http://example.org/test.rm');
327         $t = $renderer->embed_url($url);
328         $this->assertTrue(self::str_contains($t, 'CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA'));
330         // Format: youtube.
331         $url = new moodle_url('http://www.youtube.com/watch?v=vyrwMmsufJc');
332         $t = $renderer->embed_url($url);
333         $this->assertTrue(self::str_contains($t, '</iframe>'));
334         $url = new moodle_url('http://www.youtube.com/v/vyrwMmsufJc');
335         $t = $renderer->embed_url($url);
336         $this->assertTrue(self::str_contains($t, '</iframe>'));
338         // Format: youtube playlist.
339         $url = new moodle_url('http://www.youtube.com/view_play_list?p=PL6E18E2927047B662');
340         $t = $renderer->embed_url($url);
341         $this->assertTrue(self::str_contains($t, '</object>'));
342         $url = new moodle_url('http://www.youtube.com/playlist?list=PL6E18E2927047B662');
343         $t = $renderer->embed_url($url);
344         $this->assertTrue(self::str_contains($t, '</object>'));
345         $url = new moodle_url('http://www.youtube.com/p/PL6E18E2927047B662');
346         $t = $renderer->embed_url($url);
347         $this->assertTrue(self::str_contains($t, '</object>'));
349         // Format: vimeo.
350         $url = new moodle_url('http://vimeo.com/1176321');
351         $t = $renderer->embed_url($url);
352         $this->assertTrue(self::str_contains($t, '</iframe>'));
354         // Format: html5audio.
355         $this->pretend_to_be_firefox();
356         $url = new moodle_url('http://example.org/test.ogg');
357         $t = $renderer->embed_url($url);
358         $this->assertTrue(self::str_contains($t, '</audio>'));
359     }
361     /**
362      * Same as test_embed_url MP3 test, but for slash arguments.
363      */
364     public function test_slash_arguments() {
365         global $CFG, $PAGE;
367         // Again we do not turn slasharguments actually on, because it has to
368         // work regardless of the setting of that variable in case of handling
369         // links created using previous setting.
371         // Enable MP3 and get renderer.
372         $CFG->core_media_enable_mp3 = true;
373         $renderer = new core_media_renderer_test($PAGE, '');
375         // Format: mp3.
376         $url = new moodle_url('http://example.org/pluginfile.php?file=x/y/z/test.mp3');
377         $t = $renderer->embed_url($url);
378         $this->assertTrue(self::str_contains($t, 'core_media_mp3_'));
379     }
381     /**
382      * Test for core_media_renderer embed_url.
383      * Checks the EMBED_OR_BLANK option.
384      */
385     public function test_embed_or_blank() {
386         global $CFG, $PAGE;
387         $CFG->core_media_enable_html5audio = true;
388         $this->pretend_to_be_firefox();
390         $renderer = new core_media_renderer_test($PAGE, '');
392         $options = array(core_media::OPTION_FALLBACK_TO_BLANK => true);
394         // Embed that does match something should still include the link too.
395         $url = new moodle_url('http://example.org/test.ogg');
396         $t = $renderer->embed_url($url, '', 0, 0, $options);
397         $this->assertTrue(self::str_contains($t, '</audio>'));
398         $this->assertTrue(self::str_contains($t, 'mediafallbacklink'));
400         // Embed that doesn't match something should be totally blank.
401         $url = new moodle_url('http://example.org/test.mp4');
402         $t = $renderer->embed_url($url, '', 0, 0, $options);
403         $this->assertEquals('', $t);
404     }
406     /**
407      * Test for core_media_renderer embed_url.
408      * Checks that size is passed through correctly to player objects and tests
409      * size support in html5video output.
410      */
411     public function test_embed_url_size() {
412         global $CFG, $PAGE;
414         // Technically this could break in every format and they handle size
415         // in several different ways, but I'm too lazy to test it in every
416         // format, so let's just pick one to check the values get passed
417         // through.
418         $CFG->core_media_enable_html5video = true;
419         $renderer = new core_media_renderer_test($PAGE, '');
420         $url = new moodle_url('http://example.org/test.mp4');
422         // HTML5 default size - specifies core width and does not specify height.
423         $t = $renderer->embed_url($url);
424         $this->assertTrue(self::str_contains($t, 'width="' . CORE_MEDIA_VIDEO_WIDTH . '"'));
425         $this->assertFalse(self::str_contains($t, 'height'));
427         // HTML5 specified size - specifies both.
428         $t = $renderer->embed_url($url, '', '666', '101');
429         $this->assertTrue(self::str_contains($t, 'width="666"'));
430         $this->assertTrue(self::str_contains($t, 'height="101"'));
432         // HTML5 size specified in url, overrides call.
433         $url = new moodle_url('http://example.org/test.mp4?d=123x456');
434         $t = $renderer->embed_url($url, '', '666', '101');
435         $this->assertTrue(self::str_contains($t, 'width="123"'));
436         $this->assertTrue(self::str_contains($t, 'height="456"'));
437     }
439     /**
440      * Test for core_media_renderer embed_url.
441      * Checks that name is passed through correctly to player objects and tests
442      * name support in html5video output.
443      */
444     public function test_embed_url_name() {
445         global $CFG, $PAGE;
447         // As for size this could break in every format but I'm only testing
448         // html5video.
449         $CFG->core_media_enable_html5video = true;
450         $renderer = new core_media_renderer_test($PAGE, '');
451         $url = new moodle_url('http://example.org/test.mp4');
453         // HTML5 default name - use filename.
454         $t = $renderer->embed_url($url);
455         $this->assertTrue(self::str_contains($t, 'title="test.mp4"'));
457         // HTML5 specified name - check escaping.
458         $t = $renderer->embed_url($url, 'frog & toad');
459         $this->assertTrue(self::str_contains($t, 'title="frog &amp; toad"'));
460     }
462     /**
463      * Test for core_media_renderer split_alternatives.
464      */
465     public function test_split_alternatives() {
466         // Single URL - identical moodle_url.
467         $mp4 = 'http://example.org/test.mp4';
468         $result = core_media::split_alternatives($mp4, $w, $h);
469         $this->assertEquals($mp4, $result[0]->out(false));
471         // Width and height weren't specified.
472         $this->assertEquals(0, $w);
473         $this->assertEquals(0, $h);
475         // Two URLs - identical moodle_urls.
476         $webm = 'http://example.org/test.webm';
477         $result = core_media::split_alternatives("$mp4#$webm", $w, $h);
478         $this->assertEquals($mp4, $result[0]->out(false));
479         $this->assertEquals($webm, $result[1]->out(false));
481         // Two URLs plus dimensions.
482         $size = 'd=400x280';
483         $result = core_media::split_alternatives("$mp4#$webm#$size", $w, $h);
484         $this->assertEquals($mp4, $result[0]->out(false));
485         $this->assertEquals($webm, $result[1]->out(false));
486         $this->assertEquals(400, $w);
487         $this->assertEquals(280, $h);
489         // Two URLs plus legacy dimensions (use last one).
490         $result = core_media::split_alternatives("$mp4?d=1x1#$webm?$size", $w, $h);
491         $this->assertEquals($mp4, $result[0]->out(false));
492         $this->assertEquals($webm, $result[1]->out(false));
493         $this->assertEquals(400, $w);
494         $this->assertEquals(280, $h);
495     }
497     /**
498      * Test for core_media_renderer embed_alternatives (with multiple urls)
499      */
500     public function test_embed_alternatives() {
501         global $PAGE, $CFG;
503         // Most aspects of this are same as single player so let's just try
504         // a single typical / complicated scenario.
506         // MP3, WebM and FLV.
507         $urls = array(
508             new moodle_url('http://example.org/test.mp4'),
509             new moodle_url('http://example.org/test.webm'),
510             new moodle_url('http://example.org/test.flv'),
511         );
513         // Enable html5 and flv.
514         $CFG->core_media_enable_html5video = true;
515         $CFG->core_media_enable_flv = true;
516         $renderer = new core_media_renderer_test($PAGE, '');
518         // Result should contain HTML5 with two sources + FLV.
519         $t = $renderer->embed_alternatives($urls);
521         // HTML5 sources - mp4, not flv or webm (not supported in Safari).
522         $this->assertTrue(self::str_contains($t, '<source src="http://example.org/test.mp4"'));
523         $this->assertFalse(self::str_contains($t, '<source src="http://example.org/test.webm"'));
524         $this->assertFalse(self::str_contains($t, '<source src="http://example.org/test.flv"'));
526         // FLV is before the video tag (indicating html5 is used as fallback to flv
527         // and not vice versa).
528         $this->assertTrue((bool)preg_match('~core_media_flv_.*<video~s', $t));
530         // Do same test with firefox and check we get the webm and not mp4.
531         $this->pretend_to_be_firefox();
532         $t = $renderer->embed_alternatives($urls);
534         // HTML5 sources - webm, not not flv or mp4 (not supported in Firefox).
535         $this->assertFalse(self::str_contains($t, '<source src="http://example.org/test.mp4"'));
536         $this->assertTrue(self::str_contains($t, '<source src="http://example.org/test.webm"'));
537         $this->assertFalse(self::str_contains($t, '<source src="http://example.org/test.flv"'));
538     }
540     /**
541      * Checks if text contains a given string. Should be PHP built-in function,
542      * but oddly isn't.
543      *
544      * @param string $text Text
545      * @param string $value Value that should be contained within the text
546      * @return bool True if value is contained
547      */
548     public static function str_contains($text, $value) {
549         return strpos($text, $value) !== false;
550     }
552     /**
553      * Converts moodle_url array into a single comma-separated string for
554      * easier testing.
555      *
556      * @param array $urls Array of moodle_urls
557      * @return string String containing those URLs, comma-separated
558      */
559     public static function string_urls($urls) {
560         $out = array();
561         foreach ($urls as $url) {
562             $out[] = $url->out(false);
563         }
564         return implode(',', $out);
565     }
567     /**
568      * Converts associative array into a semicolon-separated string for easier
569      * testing.
570      *
571      * @param array $options Associative array
572      * @return string String of form 'a=b;c=d'
573      */
574     public static function string_options($options) {
575         $out = '';
576         foreach ($options as $key => $value) {
577             if ($out) {
578                 $out .= ';';
579             }
580             $out .= "$key=$value";
581         }
582         return $out;
583     }
586 /**
587  * Media player stub for testing purposes.
588  */
589 class core_media_player_test extends core_media_player {
590     /** @var array Array of supported extensions */
591     public $ext;
592     /** @var int Player rank */
593     public $rank;
594     /** @var int Arbitrary number */
595     public $num;
597     /**
598      * @param int $num Number (used in output)
599      * @param int $rank Player rank
600      * @param array $ext Array of supported extensions
601      */
602     public function __construct($num = 1, $rank = 13, $ext = array('tst', 'test')) {
603         $this->ext = $ext;
604         $this->rank = $rank;
605         $this->num = $num;
606     }
608     public function embed($urls, $name, $width, $height, $options) {
609         return $this->num . ':' . medialib_test::string_urls($urls) .
610                 ",$name,$width,$height,<!--FALLBACK-->," . medialib_test::string_options($options);
611     }
613     public function get_supported_extensions() {
614         return $this->ext;
615     }
617     public function get_rank() {
618         return $this->rank;
619     }
622 /**
623  * Media renderer override for testing purposes.
624  */
625 class core_media_renderer_test extends core_media_renderer {
626     /**
627      * Access list of players as string, shortening it by getting rid of
628      * repeated text.
629      * @return string Comma-separated list of players
630      */
631     public function get_players_test() {
632         $players = $this->get_players();
633         $out = '';
634         foreach ($players as $player) {
635             if ($out) {
636                 $out .= ', ';
637             }
638             $out .= str_replace('core_media_player_', '', get_class($player));
639         }
640         return $out;
641     }