355fefe4e7baa1df1c058b313d4b007719de0f8d
[moodle.git] / lib / form / tests / filetypes_util_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  * Provides the {@link core_form\filetypes_util_testcase} class.
19  *
20  * @package     core_form
21  * @category    test
22  * @copyright   2017 David Mudrák <david@moodle.com>
23  * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 namespace core_form;
28 use advanced_testcase;
30 defined('MOODLE_INTERNAL') || die();
32 global $CFG;
34 /**
35  * Test cases for the {@link core_form\filetypes_util} class.
36  *
37  * @copyright 2017 David Mudrak <david@moodle.com>
38  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39  */
40 class filetypes_util_testcase extends advanced_testcase {
42     /**
43      * Test normalizing list of extensions.
44      */
45     public function test_normalize_file_types() {
47         $this->resetAfterTest(true);
48         $util = new filetypes_util();
50         $this->assertSame(['.odt'], $util->normalize_file_types('.odt'));
51         $this->assertSame(['.odt'], $util->normalize_file_types('odt'));
52         $this->assertSame(['.odt'], $util->normalize_file_types('.ODT'));
53         $this->assertSame(['.doc', '.jpg', '.mp3'], $util->normalize_file_types('doc, jpg, mp3'));
54         $this->assertSame(['.doc', '.jpg', '.mp3'], $util->normalize_file_types(['.doc', '.jpg', '.mp3']));
55         $this->assertSame(['.doc', '.jpg', '.mp3'], $util->normalize_file_types('doc, *.jpg, mp3'));
56         $this->assertSame(['.doc', '.jpg', '.mp3'], $util->normalize_file_types(['doc ', ' JPG ', '.mp3']));
57         $this->assertSame(['.rtf', '.pdf', '.docx'],
58             $util->normalize_file_types("RTF,.pdf\n...DocX,,,;\rPDF\trtf ...Rtf"));
59         $this->assertSame(['.tgz', '.tar.gz'], $util->normalize_file_types('tgz,TAR.GZ tar.gz .tar.gz tgz TGZ'));
60         $this->assertSame(['.notebook'], $util->normalize_file_types('"Notebook":notebook;NOTEBOOK;,\'NoTeBook\''));
61         $this->assertSame([], $util->normalize_file_types(''));
62         $this->assertSame([], $util->normalize_file_types([]));
63         $this->assertSame(['.0'], $util->normalize_file_types(0));
64         $this->assertSame(['.0'], $util->normalize_file_types('0'));
65         $this->assertSame(['.odt'], $util->normalize_file_types('*.odt'));
66         $this->assertSame([], $util->normalize_file_types('.'));
67         $this->assertSame(['.foo'], $util->normalize_file_types('. foo'));
68         $this->assertSame(['*'], $util->normalize_file_types('*'));
69         $this->assertSame([], $util->normalize_file_types('*~'));
70         $this->assertSame(['.pdf', '.ps'], $util->normalize_file_types('pdf *.ps foo* *bar .r??'));
71         $this->assertSame(['*'], $util->normalize_file_types('pdf *.ps foo* * *bar .r??'));
72     }
74     /**
75      * Test MIME type formal recognition.
76      */
77     public function test_looks_like_mimetype() {
79         $this->resetAfterTest(true);
80         $util = new filetypes_util();
82         $this->assertTrue($util->looks_like_mimetype('type/subtype'));
83         $this->assertTrue($util->looks_like_mimetype('type/x-subtype'));
84         $this->assertTrue($util->looks_like_mimetype('type/x-subtype+xml'));
85         $this->assertTrue($util->looks_like_mimetype('type/vnd.subtype.xml'));
86         $this->assertTrue($util->looks_like_mimetype('type/vnd.subtype+xml'));
88         $this->assertFalse($util->looks_like_mimetype('.gif'));
89         $this->assertFalse($util->looks_like_mimetype('audio'));
90         $this->assertFalse($util->looks_like_mimetype('foo/bar/baz'));
91     }
93     /**
94      * Test getting/checking group.
95      */
96     public function test_is_filetype_group() {
98         $this->resetAfterTest(true);
99         $util = new filetypes_util();
101         $audio = $util->is_filetype_group('audio');
102         $this->assertNotFalse($audio);
103         $this->assertInternalType('array', $audio->extensions);
104         $this->assertInternalType('array', $audio->mimetypes);
106         $this->assertFalse($util->is_filetype_group('.gif'));
107         $this->assertFalse($util->is_filetype_group('somethingveryunlikelytoeverexist'));
108     }
111     /**
112      * Test describing list of extensions.
113      */
114     public function test_describe_file_types() {
116         $this->resetAfterTest(true);
117         $util = new filetypes_util();
119         force_current_language('en');
121         // Check that it is able to describe individual file extensions.
122         $desc = $util->describe_file_types('jpg .jpeg *.jpe PNG;.gif,  mudrd8mz');
123         $this->assertTrue($desc->hasdescriptions);
125         $desc = $desc->descriptions;
126         $this->assertEquals(4, count($desc));
128         $this->assertEquals('File', $desc[0]->description);
129         $this->assertEquals('.mudrd8mz', $desc[0]->extensions);
131         $this->assertEquals('Image (JPEG)', $desc[2]->description);
132         $this->assertContains('.jpg', $desc[2]->extensions);
133         $this->assertContains('.jpeg', $desc[2]->extensions);
134         $this->assertContains('.jpe', $desc[2]->extensions);
136         // Check that it can describe groups and mimetypes too.
137         $desc = $util->describe_file_types('audio text/plain');
138         $this->assertTrue($desc->hasdescriptions);
140         $desc = $desc->descriptions;
141         $this->assertEquals(2, count($desc));
143         $this->assertEquals('Audio files', $desc[0]->description);
144         $this->assertContains('.mp3', $desc[0]->extensions);
145         $this->assertContains('.wav', $desc[0]->extensions);
146         $this->assertContains('.ogg', $desc[0]->extensions);
148         $this->assertEquals('Text file', $desc[1]->description);
149         $this->assertContains('.txt', $desc[1]->extensions);
151         // Empty.
152         $desc = $util->describe_file_types('');
153         $this->assertFalse($desc->hasdescriptions);
154         $this->assertEmpty($desc->descriptions);
156         // Any.
157         $desc = $util->describe_file_types('*');
158         $this->assertTrue($desc->hasdescriptions);
159         $this->assertNotEmpty($desc->descriptions[0]->description);
160         $this->assertEmpty($desc->descriptions[0]->extensions);
162         // Unknown mimetype.
163         $desc = $util->describe_file_types('application/x-something-really-unlikely-ever-exist');
164         $this->assertTrue($desc->hasdescriptions);
165         $this->assertEquals('application/x-something-really-unlikely-ever-exist', $desc->descriptions[0]->description);
166         $this->assertEmpty($desc->descriptions[0]->extensions);
167     }
169     /**
170      * Test expanding mime types into extensions.
171      */
172     public function test_expand() {
174         $this->resetAfterTest(true);
175         $util = new filetypes_util();
177         $this->assertSame([], $util->expand(''));
179         $expanded = $util->expand('document .cdr text/plain');
180         $this->assertNotContains('document', $expanded);
181         $this->assertNotContains('text/plain', $expanded);
182         $this->assertContains('.doc', $expanded);
183         $this->assertContains('.odt', $expanded);
184         $this->assertContains('.txt', $expanded);
185         $this->assertContains('.cdr', $expanded);
187         $expanded = $util->expand('document .cdr text/plain', true, false);
188         $this->assertContains('document', $expanded);
189         $this->assertNotContains('text/plain', $expanded);
190         $this->assertContains('.doc', $expanded);
191         $this->assertContains('.odt', $expanded);
192         $this->assertContains('.txt', $expanded);
193         $this->assertContains('.cdr', $expanded);
195         $expanded = $util->expand('document .cdr text/plain', false, true);
196         $this->assertNotContains('document', $expanded);
197         $this->assertContains('text/plain', $expanded);
198         $this->assertContains('.doc', $expanded);
199         $this->assertContains('.odt', $expanded);
200         $this->assertContains('.txt', $expanded);
201         $this->assertContains('.cdr', $expanded);
203         $this->assertSame([], $util->expand('foo/bar', true, false));
204         $this->assertSame(['foo/bar'], $util->expand('foo/bar', true, true));
205     }
207     /**
208      * Test checking that a type is among others.
209      */
210     public function test_is_whitelisted() {
212         $this->resetAfterTest(true);
213         $util = new filetypes_util();
215         // These should be intuitively true.
216         $this->assertTrue($util->is_whitelisted('txt', 'text/plain'));
217         $this->assertTrue($util->is_whitelisted('txt', 'doc txt rtf'));
218         $this->assertTrue($util->is_whitelisted('.txt', '.doc;.txt;.rtf'));
219         $this->assertTrue($util->is_whitelisted('audio', 'text/plain audio video'));
220         $this->assertTrue($util->is_whitelisted('text/plain', 'text/plain audio video'));
221         $this->assertTrue($util->is_whitelisted('jpg jpe jpeg', 'image/jpeg'));
222         $this->assertTrue($util->is_whitelisted(['jpg', 'jpe', '.png'], 'image'));
224         // These should be intuitively false.
225         $this->assertFalse($util->is_whitelisted('.gif', 'text/plain'));
227         // Not all text/plain formats are in the document group.
228         $this->assertFalse($util->is_whitelisted('text/plain', 'document'));
230         // Not all documents (and also the group itself) is not a plain text.
231         $this->assertFalse($util->is_whitelisted('document', 'text/plain'));
233         // This may look wrong at the first sight as you might expect that the
234         // mimetype should simply map to an extension ...
235         $this->assertFalse($util->is_whitelisted('image/jpeg', '.jpg'));
237         // But it is principally same situation as this (there is no 1:1 mapping).
238         $this->assertFalse($util->is_whitelisted('.c', '.txt'));
239         $this->assertTrue($util->is_whitelisted('.txt .c', 'text/plain'));
240         $this->assertFalse($util->is_whitelisted('text/plain', '.c'));
242         // Any type is included if the filter is empty.
243         $this->assertTrue($util->is_whitelisted('txt', ''));
244         $this->assertTrue($util->is_whitelisted('txt', '*'));
246         // Empty value is part of any whitelist.
247         $this->assertTrue($util->is_whitelisted('', '.txt'));
248     }
250     /**
251      * Test getting types not present in a whitelist.
252      */
253     public function test_get_not_whitelisted() {
255         $this->resetAfterTest(true);
256         $util = new filetypes_util();
258         $this->assertEmpty($util->get_not_whitelisted('txt', 'text/plain'));
259         $this->assertEmpty($util->get_not_whitelisted('txt', '.doc .txt .rtf'));
260         $this->assertEmpty($util->get_not_whitelisted('txt', 'text/plain'));
261         $this->assertEmpty($util->get_not_whitelisted(['jpg', 'jpe', 'jpeg'], 'image/jpeg'));
262         $this->assertEmpty($util->get_not_whitelisted('', 'foo/bar'));
263         $this->assertEmpty($util->get_not_whitelisted('.foobar', ''));
264         $this->assertEmpty($util->get_not_whitelisted('.foobar', '*'));
266         // Returned list is normalized so extensions have the dot added.
267         $this->assertContains('.exe', $util->get_not_whitelisted('exe', '.c .h'));
269         // If this looks wrong to you, see {@link test_is_whitelisted()} for more details on this behaviour.
270         $this->assertContains('image/jpeg', $util->get_not_whitelisted('image/jpeg', '.jpg .jpeg'));
271     }
273     /**
274      * Test populating the tree for the browser.
275      */
276     public function test_data_for_browser() {
278         $this->resetAfterTest(true);
279         $util = new filetypes_util();
281         $data = $util->data_for_browser();
282         $this->assertContainsOnly('object', $data);
283         foreach ($data as $group) {
284             $this->assertObjectHasAttribute('key', $group);
285             $this->assertObjectHasAttribute('types', $group);
286             if ($group->key !== '') {
287                 $this->assertTrue($group->selectable);
288             }
289         }
291         // Confirm that the reserved type '.xxx' isn't present in the 'Other files' section.
292         $types = array_reduce($data, function($carry, $group) {
293             if ($group->name === 'Other files') {
294                 return $group->types;
295             }
296         });
297         $typekeys = array_map(function($type) {
298             return $type->key;
299         }, $types);
300         $this->assertNotContains('.xxx', $typekeys);
302         // All these three files are in both "image" and also "web_image"
303         // groups. We display both groups.
304         $data = $util->data_for_browser('jpg png gif', true, '.gif');
305         $this->assertEquals(3, count($data));
306         $this->assertTrue($data[0]->key !== $data[1]->key);
307         foreach ($data as $group) {
308             $this->assertTrue(($group->key === 'image' || $group->key === 'web_image' || $group->key === 'optimised_image'));
309             $this->assertEquals(3, count($group->types));
310             $this->assertFalse($group->selectable);
311             foreach ($group->types as $ext) {
312                 if ($ext->key === '.gif') {
313                     $this->assertTrue($ext->selected);
314                 } else {
315                     $this->assertFalse($ext->selected);
316                 }
317             }
318         }
320         // The groups web_image and optimised_image are a subset of the group image. The
321         // file extensions that fall into these groups will be displayed thrice.
322         $data = $util->data_for_browser('web_image');
323         foreach ($data as $group) {
324             $this->assertTrue(($group->key === 'image' || $group->key === 'web_image' || $group->key === 'optimised_image'));
325         }
327         // Check that "All file types" are displayed first.
328         $data = $util->data_for_browser();
329         $group = array_shift($data);
330         $this->assertEquals('*', $group->key);
332         // Check that "All file types" is not displayed if should not.
333         $data = $util->data_for_browser(null, false);
334         $group = array_shift($data);
335         $this->assertNotEquals('*', $group->key);
337         // Groups with an extension selected start expanded. The "Other files"
338         // starts expanded. The rest start collapsed.
339         $data = $util->data_for_browser(null, false, '.png');
340         foreach ($data as $group) {
341             if ($group->key === 'document') {
342                 $this->assertfalse($group->expanded);
343             } else if ($group->key === '') {
344                 $this->assertTrue($group->expanded);
345             }
346             foreach ($group->types as $ext) {
347                 foreach ($group->types as $ext) {
348                     if ($ext->key === '.png') {
349                         $this->assertTrue($ext->selected);
350                         $this->assertTrue($group->expanded);
351                     }
352                 }
353             }
354         }
355     }
357     /**
358      * Data provider for testing test_is_allowed_file_type.
359      *
360      * @return array
361      */
362     public function is_allowed_file_type_provider() {
363         return [
364             'Filetype not in extension whitelist' => [
365                 'filename' => 'test.xml',
366                 'whitelist' => '.png .jpg',
367                 'expected' => false
368             ],
369             'Filetype not in mimetype whitelist' => [
370                 'filename' => 'test.xml',
371                 'whitelist' => 'image/png',
372                 'expected' => false
373             ],
374             'Filetype not in group whitelist' => [
375                 'filename' => 'test.xml',
376                 'whitelist' => 'web_file',
377                 'expected' => false
378             ],
379             'Filetype in whitelist as extension' => [
380                 'filename' => 'test.xml',
381                 'whitelist' => 'xml',
382                 'expected' => true
383             ],
384             'Empty whitelist should allow all' => [
385                 'filename' => 'test.xml',
386                 'whitelist' => '',
387                 'expected' => true
388             ],
389             'Filetype in whitelist but later on' => [
390                 'filename' => 'test.xml',
391                 'whitelist' => 'gif;jpeg,image/png xml xlsx',
392                 'expected' => true
393             ],
394             'Filetype in whitelist as mimetype' => [
395                 'filename' => 'test.xml',
396                 'whitelist' => 'image/png application/xml',
397                 'expected' => true
398             ],
399             'Filetype in whitelist as group' => [
400                 'filename' => 'test.html',
401                 'whitelist' => 'video,web_file',
402                 'expected' => true
403             ],
404         ];
405     }
407     /**
408      * Test is_allowed_file_type().
409      * @dataProvider is_allowed_file_type_provider
410      * @param string $filename The filename to check
411      * @param string $whitelist The space , or ; separated list of types supported
412      * @param boolean $expected The expected result. True if the file is allowed, false if not.
413      */
414     public function test_is_allowed_file_type($filename, $whitelist, $expected) {
415         $util = new filetypes_util();
416         $this->assertSame($expected, $util->is_allowed_file_type($filename, $whitelist));
417     }
419     /**
420      * Data provider for testing test_get_unknown_file_types.
421      *
422      * @return array
423      */
424     public function get_unknown_file_types_provider() {
425         return [
426             'Empty list' => [
427                 'filetypes' => '',
428                 'expected' => [],
429             ],
430             'Any file type' => [
431                 'filetypes' => '*',
432                 'expected' => [],
433             ],
434             'Unknown extension' => [
435                 'filetypes' => '.rat',
436                 'expected' => ['.rat']
437             ],
438             'Multiple unknown extensions' => [
439                 'filetypes' => '.ricefield .rat',
440                 'expected' => ['.ricefield', '.rat']
441             ],
442             'Existant extension' => [
443                 'filetypes' => '.xml',
444                 'expected' => []
445             ],
446             'Existant group' => [
447                 'filetypes' => 'web_file',
448                 'expected' => []
449             ],
450             'Nonexistant mimetypes' => [
451                 'filetypes' => 'ricefield/rat',
452                 'expected' => ['ricefield/rat']
453             ],
454             'Existant mimetype' => [
455                 'filetypes' => 'application/xml',
456                 'expected' => []
457             ],
458             'Multiple unknown mimetypes' => [
459                 'filetypes' => 'ricefield/rat cam/ball',
460                 'expected' => ['ricefield/rat', 'cam/ball']
461             ],
462             'Strange characters in unknown extension/group' => [
463                 'filetypes' => '©ç√√ß∂å√©åß©√',
464                 'expected' => ['.©ç√√ß∂å√©åß©√']
465             ],
466             'Some existant some not' => [
467                 'filetypes' => '.txt application/xml web_file ©ç√√ß∂å√©åß©√ .png ricefield/rat document',
468                 'expected' => ['.©ç√√ß∂å√©åß©√', 'ricefield/rat']
469             ],
470             'Reserved file type xxx included' => [
471                 'filetypes' => '.xxx .html .jpg',
472                 'expected' => ['.xxx']
473             ]
474         ];
475     }
477     /**
478      * Test get_unknown_file_types().
479      * @dataProvider get_unknown_file_types_provider
480      * @param string $filetypes The filetypes to check
481      * @param array $expected The expected result. The list of non existant file types.
482      */
483     public function test_get_unknown_file_types($filetypes, $expected) {
484         $util = new filetypes_util();
485         $this->assertSame($expected, $util->get_unknown_file_types($filetypes));
486     }