a4181d4deab1744134cd27a55acaeb8a9722bc25
[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.
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      * Post-test cleanup. Replaces old $CFG.
86      */
87     public function tearDown() {
88         // Replace original user agent.
89         if (isset($this->realserver)) {
90             $_SERVER = $this->realserver;
91         }
93         parent::tearDown();
94     }
96     /**
97      * Test for the core_media_player is_enabled.
98      */
99     public function test_is_enabled() {
100         global $CFG;
102         // Test enabled: unset, 0, 1.
103         $test = new core_media_player_test;
104         $this->assertFalse($test->is_enabled());
105         $CFG->core_media_enable_test = 0;
106         $this->assertFalse($test->is_enabled());
107         $CFG->core_media_enable_test = 1;
108         $this->assertTrue($test->is_enabled());
109     }
111     /**
112      * Test for the core_media_player list_supported_urls.
113      */
114     public function test_list_supported_urls() {
115         global $CFG;
116         $test = new core_media_player_test;
118         // Some example URLs.
119         $supported1 = new moodle_url('http://example.org/1.test');
120         $supported2 = new moodle_url('http://example.org/2.TST');
121         $unsupported = new moodle_url('http://example.org/2.jpg');
123         // No URLs => none.
124         $result = $test->list_supported_urls(array());
125         $this->assertEquals(array(), $result);
127         // One supported URL => same.
128         $result = $test->list_supported_urls(array($supported1));
129         $this->assertEquals(array($supported1), $result);
131         // Two supported URLS => same.
132         $result = $test->list_supported_urls(array($supported1, $supported2));
133         $this->assertEquals(array($supported1, $supported2), $result);
135         // One unsupported => none.
136         $result = $test->list_supported_urls(array($unsupported));
137         $this->assertEquals(array(), $result);
139         // Two supported and one unsupported => same.
140         $result = $test->list_supported_urls(array($supported2, $unsupported, $supported1));
141         $this->assertEquals(array($supported2, $supported1), $result);
142     }
144     /**
145      * Test for core_media_renderer get_players
146      */
147     public function test_get_players() {
148         global $CFG, $PAGE;
150         // All players are initially disabled (except link, which you can't).
151         $renderer = new core_media_renderer_test($PAGE, '');
152         $this->assertEquals('link', $renderer->get_players_test());
154         // A couple enabled, check the order.
155         $CFG->core_media_enable_html5audio = true;
156         $CFG->core_media_enable_mp3 = true;
157         $renderer = new core_media_renderer_test($PAGE, '');
158         $this->assertEquals('mp3, html5audio, link', $renderer->get_players_test());
159     }
161     /**
162      * Test for core_media_renderer can_embed_url
163      */
164     public function test_can_embed_url() {
165         global $CFG, $PAGE;
167         // All players are initially disabled, so mp4 cannot be rendered.
168         $url = new moodle_url('http://example.org/test.mp4');
169         $renderer = new core_media_renderer_test($PAGE, '');
170         $this->assertFalse($renderer->can_embed_url($url));
172         // Enable QT player.
173         $CFG->core_media_enable_qt = true;
174         $renderer = new core_media_renderer_test($PAGE, '');
175         $this->assertTrue($renderer->can_embed_url($url));
177         // QT + html5.
178         $CFG->core_media_enable_html5video = true;
179         $renderer = new core_media_renderer_test($PAGE, '');
180         $this->assertTrue($renderer->can_embed_url($url));
182         // Only html5.
183         $CFG->core_media_enable_qt = false;
184         $renderer = new core_media_renderer_test($PAGE, '');
185         $this->assertTrue($renderer->can_embed_url($url));
187         // Only WMP.
188         $CFG->core_media_enable_html5video = false;
189         $CFG->core_media_enable_wmp = true;
190         $renderer = new core_media_renderer_test($PAGE, '');
191         $this->assertFalse($renderer->can_embed_url($url));
192     }
194     /**
195      * Test for core_media_renderer embed_url.
196      * Checks multiple format/fallback support.
197      */
198     public function test_embed_url_fallbacks() {
199         global $CFG, $PAGE;
201         $url = new moodle_url('http://example.org/test.mp4');
203         // All plugins disabled, NOLINK option.
204         $renderer = new core_media_renderer_test($PAGE, '');
205         $t = $renderer->embed_url($url, 0, 0, '',
206                 array(core_media::OPTION_NO_LINK => true));
207         // Completely empty.
208         $this->assertEquals('', $t);
210         // All plugins disabled but not NOLINK.
211         $renderer = new core_media_renderer_test($PAGE, '');
212         $t = $renderer->embed_url($url);
213         $this->assert_contents(false, false, true, $t);
215         // HTML5 plugin enabled.
216         $CFG->core_media_enable_html5video = true;
217         $renderer = new core_media_renderer_test($PAGE, '');
218         $t = $renderer->embed_url($url);
219         $this->assert_contents(false, true, true, $t);
221         // QT plugin enabled as well.
222         $CFG->core_media_enable_qt = true;
223         $renderer = new core_media_renderer_test($PAGE, '');
224         $t = $renderer->embed_url($url);
225         $this->assert_contents(true, true, true, $t);
227         // Nolink turned off, plugins still enabled.
228         $t = $renderer->embed_url($url, 0, 0, '',
229                 array(core_media::OPTION_NO_LINK => true));
230         $this->assert_contents(true, true, false, $t);
231     }
233     /**
234      * Checks the contents of the resulting HTML code to ensure it used the
235      * correct embed method(s).
236      *
237      * @param bool $hasqt True if it should have QT embed code
238      * @param bool $hashtml5 True if it should have HTML5 embed code
239      * @param bool $haslink True if it should have a fallback link
240      * @param string $text HTML content
241      */
242     private function assert_contents($hasqt, $hashtml5, $haslink, $text) {
243         // I tried to avoid making it specific to the exact details of the html
244         // code, picking out only single key strings that would let it check
245         // whether final code contains the right things.
246         $qt = 'qtplugin.cab';
247         $html5 = '</video>';
248         $link = 'mediafallbacklink';
250         $this->assertEquals($hasqt, self::str_contains($text, $qt));
251         $this->assertEquals($hashtml5, self::str_contains($text, $html5));
252         $this->assertEquals($haslink, self::str_contains($text, $link));
253     }
255     /**
256      * Test for core_media_renderer embed_url.
257      * Check SWF works including the special option required to enable it
258      */
259     public function test_embed_url_swf() {
260         global $CFG, $PAGE;
261         $CFG->core_media_enable_swf = true;
262         $renderer = new core_media_renderer_test($PAGE, '');
264         // Without any options...
265         $url = new moodle_url('http://example.org/test.swf');
266         $t = $renderer->embed_url($url);
267         $this->assertFalse(self::str_contains($t, '</object>'));
269         // ...and with the 'no it's safe, I checked it' option.
270         $url = new moodle_url('http://example.org/test.swf');
271         $t = $renderer->embed_url($url, '', 0, 0, array(core_media::OPTION_TRUSTED => true));
272         $this->assertTrue(self::str_contains($t, '</object>'));
273     }
275     /**
276      * Test for core_media_renderer embed_url.
277      * Exercises all the basic formats not covered elsewhere.
278      */
279     public function test_embed_url_other_formats() {
280         global $CFG, $PAGE;
282         // Enable all players and get renderer.
283         $CFG->core_media_enable_html5audio = true;
284         $CFG->core_media_enable_mp3 = true;
285         $CFG->core_media_enable_flv = true;
286         $CFG->core_media_enable_wmp = true;
287         $CFG->core_media_enable_rm = true;
288         $CFG->core_media_enable_youtube = true;
289         $CFG->core_media_enable_vimeo = true;
290         $renderer = new core_media_renderer_test($PAGE, '');
292         // Check each format one at a time. This is a basic check to be sure
293         // the HTML is included for files of the right type, not a test that
294         // the HTML itself is correct.
296         // Format: mp3.
297         $url = new moodle_url('http://example.org/test.mp3');
298         $t = $renderer->embed_url($url);
299         $this->assertTrue(self::str_contains($t, 'core_media_mp3_'));
301         // Format: flv.
302         $url = new moodle_url('http://example.org/test.flv');
303         $t = $renderer->embed_url($url);
304         $this->assertTrue(self::str_contains($t, 'core_media_flv_'));
306         // Format: wmp.
307         $url = new moodle_url('http://example.org/test.avi');
308         $t = $renderer->embed_url($url);
309         $this->assertTrue(self::str_contains($t, '6BF52A52-394A-11d3-B153-00C04F79FAA6'));
311         // Format: rm.
312         $url = new moodle_url('http://example.org/test.rm');
313         $t = $renderer->embed_url($url);
314         $this->assertTrue(self::str_contains($t, 'CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA'));
316         // Format: youtube.
317         $url = new moodle_url('http://www.youtube.com/watch?v=vyrwMmsufJc');
318         $t = $renderer->embed_url($url);
319         $this->assertTrue(self::str_contains($t, '</iframe>'));
320         $url = new moodle_url('http://www.youtube.com/v/vyrwMmsufJc');
321         $t = $renderer->embed_url($url);
322         $this->assertTrue(self::str_contains($t, '</iframe>'));
324         // Format: youtube playlist.
325         $url = new moodle_url('http://www.youtube.com/view_play_list?p=PL6E18E2927047B662');
326         $t = $renderer->embed_url($url);
327         $this->assertTrue(self::str_contains($t, '</object>'));
328         $url = new moodle_url('http://www.youtube.com/playlist?list=PL6E18E2927047B662');
329         $t = $renderer->embed_url($url);
330         $this->assertTrue(self::str_contains($t, '</object>'));
331         $url = new moodle_url('http://www.youtube.com/p/PL6E18E2927047B662');
332         $t = $renderer->embed_url($url);
333         $this->assertTrue(self::str_contains($t, '</object>'));
335         // Format: vimeo.
336         $url = new moodle_url('http://vimeo.com/1176321');
337         $t = $renderer->embed_url($url);
338         $this->assertTrue(self::str_contains($t, '</iframe>'));
340         // Format: html5audio.
341         $this->pretend_to_be_firefox();
342         $url = new moodle_url('http://example.org/test.ogg');
343         $t = $renderer->embed_url($url);
344         $this->assertTrue(self::str_contains($t, '</audio>'));
345     }
347     /**
348      * Test for core_media_renderer embed_url.
349      * Checks the EMBED_OR_BLANK option.
350      */
351     public function test_embed_or_blank() {
352         global $CFG, $PAGE;
353         $CFG->core_media_enable_html5audio = true;
354         $this->pretend_to_be_firefox();
356         $renderer = new core_media_renderer_test($PAGE, '');
358         $options = array(core_media::OPTION_FALLBACK_TO_BLANK => true);
360         // Embed that does match something should still include the link too.
361         $url = new moodle_url('http://example.org/test.ogg');
362         $t = $renderer->embed_url($url, '', 0, 0, $options);
363         $this->assertTrue(self::str_contains($t, '</audio>'));
364         $this->assertTrue(self::str_contains($t, 'mediafallbacklink'));
366         // Embed that doesn't match something should be totally blank.
367         $url = new moodle_url('http://example.org/test.mp4');
368         $t = $renderer->embed_url($url, '', 0, 0, $options);
369         $this->assertEquals('', $t);
370     }
372     /**
373      * Test for core_media_renderer embed_url.
374      * Checks that size is passed through correctly to player objects and tests
375      * size support in html5video output.
376      */
377     public function test_embed_url_size() {
378         global $CFG, $PAGE;
380         // Technically this could break in every format and they handle size
381         // in several different ways, but I'm too lazy to test it in every
382         // format, so let's just pick one to check the values get passed
383         // through.
384         $CFG->core_media_enable_html5video = true;
385         $renderer = new core_media_renderer_test($PAGE, '');
386         $url = new moodle_url('http://example.org/test.mp4');
388         // HTML5 default size - specifies core width and does not specify height.
389         $t = $renderer->embed_url($url);
390         $this->assertTrue(self::str_contains($t, 'width="' . CORE_MEDIA_VIDEO_WIDTH . '"'));
391         $this->assertFalse(self::str_contains($t, 'height'));
393         // HTML5 specified size - specifies both.
394         $t = $renderer->embed_url($url, '', '666', '101');
395         $this->assertTrue(self::str_contains($t, 'width="666"'));
396         $this->assertTrue(self::str_contains($t, 'height="101"'));
398         // HTML5 size specified in url, overrides call.
399         $url = new moodle_url('http://example.org/test.mp4?d=123x456');
400         $t = $renderer->embed_url($url, '', '666', '101');
401         $this->assertTrue(self::str_contains($t, 'width="123"'));
402         $this->assertTrue(self::str_contains($t, 'height="456"'));
403     }
405     /**
406      * Test for core_media_renderer embed_url.
407      * Checks that name is passed through correctly to player objects and tests
408      * name support in html5video output.
409      */
410     public function test_embed_url_name() {
411         global $CFG, $PAGE;
413         // As for size this could break in every format but I'm only testing
414         // html5video.
415         $CFG->core_media_enable_html5video = true;
416         $renderer = new core_media_renderer_test($PAGE, '');
417         $url = new moodle_url('http://example.org/test.mp4');
419         // HTML5 default name - use filename.
420         $t = $renderer->embed_url($url);
421         $this->assertTrue(self::str_contains($t, 'title="test.mp4"'));
423         // HTML5 specified name - check escaping.
424         $t = $renderer->embed_url($url, 'frog & toad');
425         $this->assertTrue(self::str_contains($t, 'title="frog &amp; toad"'));
426     }
428     /**
429      * Test for core_media_renderer split_alternatives.
430      */
431     public function test_split_alternatives() {
432         // Single URL - identical moodle_url.
433         $mp4 = 'http://example.org/test.mp4';
434         $result = core_media::split_alternatives($mp4, $w, $h);
435         $this->assertEquals($mp4, $result[0]->out(false));
437         // Width and height weren't specified.
438         $this->assertEquals(0, $w);
439         $this->assertEquals(0, $h);
441         // Two URLs - identical moodle_urls.
442         $webm = 'http://example.org/test.webm';
443         $result = core_media::split_alternatives("$mp4#$webm", $w, $h);
444         $this->assertEquals($mp4, $result[0]->out(false));
445         $this->assertEquals($webm, $result[1]->out(false));
447         // Two URLs plus dimensions.
448         $size = 'd=400x280';
449         $result = core_media::split_alternatives("$mp4#$webm#$size", $w, $h);
450         $this->assertEquals($mp4, $result[0]->out(false));
451         $this->assertEquals($webm, $result[1]->out(false));
452         $this->assertEquals(400, $w);
453         $this->assertEquals(280, $h);
455         // Two URLs plus legacy dimensions (use last one).
456         $result = core_media::split_alternatives("$mp4?d=1x1#$webm?$size", $w, $h);
457         $this->assertEquals($mp4, $result[0]->out(false));
458         $this->assertEquals($webm, $result[1]->out(false));
459         $this->assertEquals(400, $w);
460         $this->assertEquals(280, $h);
461     }
463     /**
464      * Test for core_media_renderer embed_alternatives (with multiple urls)
465      */
466     public function test_embed_alternatives() {
467         global $PAGE, $CFG;
469         // Most aspects of this are same as single player so let's just try
470         // a single typical / complicated scenario.
472         // MP3, WebM and FLV.
473         $urls = array(
474             new moodle_url('http://example.org/test.mp4'),
475             new moodle_url('http://example.org/test.webm'),
476             new moodle_url('http://example.org/test.flv'),
477         );
479         // Enable html5 and flv.
480         $CFG->core_media_enable_html5video = true;
481         $CFG->core_media_enable_flv = true;
482         $renderer = new core_media_renderer_test($PAGE, '');
484         // Result should contain HTML5 with two sources + FLV.
485         $t = $renderer->embed_alternatives($urls);
487         // HTML5 sources - mp4, not flv or webm (not supported in Safari).
488         $this->assertTrue(self::str_contains($t, '<source src="http://example.org/test.mp4"'));
489         $this->assertFalse(self::str_contains($t, '<source src="http://example.org/test.webm"'));
490         $this->assertFalse(self::str_contains($t, '<source src="http://example.org/test.flv"'));
492         // FLV is before the video tag (indicating html5 is used as fallback to flv
493         // and not vice versa).
494         $this->assertTrue((bool)preg_match('~core_media_flv_.*<video~s', $t));
496         // Do same test with firefox and check we get the webm and not mp4.
497         $this->pretend_to_be_firefox();
498         $t = $renderer->embed_alternatives($urls);
500         // HTML5 sources - webm, not not flv or mp4 (not supported in Firefox).
501         $this->assertFalse(self::str_contains($t, '<source src="http://example.org/test.mp4"'));
502         $this->assertTrue(self::str_contains($t, '<source src="http://example.org/test.webm"'));
503         $this->assertFalse(self::str_contains($t, '<source src="http://example.org/test.flv"'));
504     }
506     /**
507      * Checks if text contains a given string. Should be PHP built-in function,
508      * but oddly isn't.
509      *
510      * @param string $text Text
511      * @param string $value Value that should be contained within the text
512      * @return bool True if value is contained
513      */
514     public static function str_contains($text, $value) {
515         return strpos($text, $value) !== false;
516     }
518     /**
519      * Converts moodle_url array into a single comma-separated string for
520      * easier testing.
521      *
522      * @param array $urls Array of moodle_urls
523      * @return string String containing those URLs, comma-separated
524      */
525     public static function string_urls($urls) {
526         $out = array();
527         foreach ($urls as $url) {
528             $out[] = $url->out(false);
529         }
530         return implode(',', $out);
531     }
533     /**
534      * Converts associative array into a semicolon-separated string for easier
535      * testing.
536      *
537      * @param array $options Associative array
538      * @return string String of form 'a=b;c=d'
539      */
540     public static function string_options($options) {
541         $out = '';
542         foreach ($options as $key => $value) {
543             if ($out) {
544                 $out .= ';';
545             }
546             $out .= "$key=$value";
547         }
548         return $out;
549     }
552 /**
553  * Media player stub for testing purposes.
554  */
555 class core_media_player_test extends core_media_player {
556     /** @var array Array of supported extensions */
557     public $ext;
558     /** @var int Player rank */
559     public $rank;
560     /** @var int Arbitrary number */
561     public $num;
563     /**
564      * @param int $num Number (used in output)
565      * @param int $rank Player rank
566      * @param array $ext Array of supported extensions
567      */
568     public function __construct($num = 1, $rank = 13, $ext = array('tst', 'test')) {
569         $this->ext = $ext;
570         $this->rank = $rank;
571         $this->num = $num;
572     }
574     public function embed($urls, $name, $width, $height, $options) {
575         return $this->num . ':' . medialib_test::string_urls($urls) .
576                 ",$name,$width,$height,<!--FALLBACK-->," . medialib_test::string_options($options);
577     }
579     public function get_supported_extensions() {
580         return $this->ext;
581     }
583     public function get_rank() {
584         return $this->rank;
585     }
588 /**
589  * Media renderer override for testing purposes.
590  */
591 class core_media_renderer_test extends core_media_renderer {
592     /**
593      * Access list of players as string, shortening it by getting rid of
594      * repeated text.
595      * @return string Comma-separated list of players
596      */
597     public function get_players_test() {
598         $players = $this->get_players();
599         $out = '';
600         foreach ($players as $player) {
601             if ($out) {
602                 $out .= ', ';
603             }
604             $out .= str_replace('core_media_player_', '', get_class($player));
605         }
606         return $out;
607     }