Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 and 403]

   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/>.
  16  
  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   */
  25  
  26  namespace core_form;
  27  
  28  use advanced_testcase;
  29  
  30  defined('MOODLE_INTERNAL') || die();
  31  
  32  global $CFG;
  33  
  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_test extends advanced_testcase {
  41  
  42      /**
  43       * Test normalizing list of extensions.
  44       */
  45      public function test_normalize_file_types() {
  46  
  47          $this->resetAfterTest(true);
  48          $util = new filetypes_util();
  49  
  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      }
  73  
  74      /**
  75       * Test MIME type formal recognition.
  76       */
  77      public function test_looks_like_mimetype() {
  78  
  79          $this->resetAfterTest(true);
  80          $util = new filetypes_util();
  81  
  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'));
  87  
  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      }
  92  
  93      /**
  94       * Test getting/checking group.
  95       */
  96      public function test_is_filetype_group() {
  97  
  98          $this->resetAfterTest(true);
  99          $util = new filetypes_util();
 100  
 101          $audio = $util->is_filetype_group('audio');
 102          $this->assertNotFalse($audio);
 103          $this->assertIsArray($audio->extensions);
 104          $this->assertIsArray($audio->mimetypes);
 105  
 106          $this->assertFalse($util->is_filetype_group('.gif'));
 107          $this->assertFalse($util->is_filetype_group('somethingveryunlikelytoeverexist'));
 108      }
 109  
 110  
 111      /**
 112       * Test describing list of extensions.
 113       */
 114      public function test_describe_file_types() {
 115  
 116          $this->resetAfterTest(true);
 117          $util = new filetypes_util();
 118  
 119          force_current_language('en');
 120  
 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);
 124  
 125          $desc = $desc->descriptions;
 126          $this->assertEquals(4, count($desc));
 127  
 128          $this->assertEquals('File', $desc[0]->description);
 129          $this->assertEquals('.mudrd8mz', $desc[0]->extensions);
 130  
 131          $this->assertEquals('Image (JPEG)', $desc[2]->description);
 132          $this->assertStringContainsString('.jpg', $desc[2]->extensions);
 133          $this->assertStringContainsString('.jpeg', $desc[2]->extensions);
 134          $this->assertStringContainsString('.jpe', $desc[2]->extensions);
 135  
 136          // Check that it can describe groups and mimetypes too.
 137          $desc = $util->describe_file_types('audio text/plain');
 138          $this->assertTrue($desc->hasdescriptions);
 139  
 140          $desc = $desc->descriptions;
 141          $this->assertEquals(2, count($desc));
 142  
 143          $this->assertEquals('Audio files', $desc[0]->description);
 144          $this->assertStringContainsString('.mp3', $desc[0]->extensions);
 145          $this->assertStringContainsString('.wav', $desc[0]->extensions);
 146          $this->assertStringContainsString('.ogg', $desc[0]->extensions);
 147  
 148          $this->assertEquals('Text file', $desc[1]->description);
 149          $this->assertStringContainsString('.txt', $desc[1]->extensions);
 150  
 151          // Empty.
 152          $desc = $util->describe_file_types('');
 153          $this->assertFalse($desc->hasdescriptions);
 154          $this->assertEmpty($desc->descriptions);
 155  
 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);
 161  
 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      }
 168  
 169      /**
 170       * Test expanding mime types into extensions.
 171       */
 172      public function test_expand() {
 173  
 174          $this->resetAfterTest(true);
 175          $util = new filetypes_util();
 176  
 177          $this->assertSame([], $util->expand(''));
 178  
 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);
 186  
 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);
 194  
 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);
 202  
 203          $this->assertSame([], $util->expand('foo/bar', true, false));
 204          $this->assertSame(['foo/bar'], $util->expand('foo/bar', true, true));
 205      }
 206  
 207      /**
 208       * Test checking that a type is among others.
 209       */
 210      public function test_is_listed() {
 211  
 212          $this->resetAfterTest(true);
 213          $util = new filetypes_util();
 214  
 215          // These should be intuitively true.
 216          $this->assertTrue($util->is_listed('txt', 'text/plain'));
 217          $this->assertTrue($util->is_listed('txt', 'doc txt rtf'));
 218          $this->assertTrue($util->is_listed('.txt', '.doc;.txt;.rtf'));
 219          $this->assertTrue($util->is_listed('audio', 'text/plain audio video'));
 220          $this->assertTrue($util->is_listed('text/plain', 'text/plain audio video'));
 221          $this->assertTrue($util->is_listed('jpg jpe jpeg', 'image/jpeg'));
 222          $this->assertTrue($util->is_listed(['jpg', 'jpe', '.png'], 'image'));
 223  
 224          // These should be intuitively false.
 225          $this->assertFalse($util->is_listed('.gif', 'text/plain'));
 226  
 227          // Not all text/plain formats are in the document group.
 228          $this->assertFalse($util->is_listed('text/plain', 'document'));
 229  
 230          // Not all documents (and also the group itself) is not a plain text.
 231          $this->assertFalse($util->is_listed('document', 'text/plain'));
 232  
 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_listed('image/jpeg', '.jpg'));
 236  
 237          // But it is principally same situation as this (there is no 1:1 mapping).
 238          $this->assertFalse($util->is_listed('.c', '.txt'));
 239          $this->assertTrue($util->is_listed('.txt .c', 'text/plain'));
 240          $this->assertFalse($util->is_listed('text/plain', '.c'));
 241  
 242          // Any type is included if the filter is empty.
 243          $this->assertTrue($util->is_listed('txt', ''));
 244          $this->assertTrue($util->is_listed('txt', '*'));
 245  
 246          // Empty value is part of any list.
 247          $this->assertTrue($util->is_listed('', '.txt'));
 248      }
 249  
 250      /**
 251       * Test getting types not present in a list.
 252       */
 253      public function test_get_not_listed() {
 254  
 255          $this->resetAfterTest(true);
 256          $util = new filetypes_util();
 257  
 258          $this->assertEmpty($util->get_not_listed('txt', 'text/plain'));
 259          $this->assertEmpty($util->get_not_listed('txt', '.doc .txt .rtf'));
 260          $this->assertEmpty($util->get_not_listed('txt', 'text/plain'));
 261          $this->assertEmpty($util->get_not_listed(['jpg', 'jpe', 'jpeg'], 'image/jpeg'));
 262          $this->assertEmpty($util->get_not_listed('', 'foo/bar'));
 263          $this->assertEmpty($util->get_not_listed('.foobar', ''));
 264          $this->assertEmpty($util->get_not_listed('.foobar', '*'));
 265  
 266          // Returned list is normalized so extensions have the dot added.
 267          $this->assertContains('.exe', $util->get_not_listed('exe', '.c .h'));
 268  
 269          // If this looks wrong to you, see {@see self::test_is_listed()} for more details on this behaviour.
 270          $this->assertContains('image/jpeg', $util->get_not_listed('image/jpeg', '.jpg .jpeg'));
 271      }
 272  
 273      /**
 274       * Test populating the tree for the browser.
 275       */
 276      public function test_data_for_browser() {
 277  
 278          $this->resetAfterTest(true);
 279          $util = new filetypes_util();
 280  
 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          }
 290  
 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);
 301  
 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          }
 319  
 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          }
 326  
 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);
 331  
 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);
 336  
 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      }
 356  
 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 list' => [
 365                  'filename' => 'test.xml',
 366                  'list' => '.png .jpg',
 367                  'expected' => false
 368              ],
 369              'Filetype not in mimetype list' => [
 370                  'filename' => 'test.xml',
 371                  'list' => 'image/png',
 372                  'expected' => false
 373              ],
 374              'Filetype not in group list' => [
 375                  'filename' => 'test.xml',
 376                  'list' => 'web_file',
 377                  'expected' => false
 378              ],
 379              'Filetype in list as extension' => [
 380                  'filename' => 'test.xml',
 381                  'list' => 'xml',
 382                  'expected' => true
 383              ],
 384              'Empty list should allow all' => [
 385                  'filename' => 'test.xml',
 386                  'list' => '',
 387                  'expected' => true
 388              ],
 389              'Filetype in list but later on' => [
 390                  'filename' => 'test.xml',
 391                  'list' => 'gif;jpeg,image/png xml xlsx',
 392                  'expected' => true
 393              ],
 394              'Filetype in list as mimetype' => [
 395                  'filename' => 'test.xml',
 396                  'list' => 'image/png application/xml',
 397                  'expected' => true
 398              ],
 399              'Filetype in list as group' => [
 400                  'filename' => 'test.html',
 401                  'list' => 'video,web_file',
 402                  'expected' => true
 403              ],
 404          ];
 405      }
 406  
 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 $list 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, $list, $expected) {
 415          $util = new filetypes_util();
 416          $this->assertSame($expected, $util->is_allowed_file_type($filename, $list));
 417      }
 418  
 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      }
 476  
 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      }
 487  
 488      /**
 489       * Test that a debugging noticed is displayed when calling is_whitelisted().
 490       */
 491      public function test_deprecation_is_whitelisted() {
 492  
 493          $util = new filetypes_util();
 494          $this->assertTrue($util->is_whitelisted('txt', 'text/plain'));
 495          $this->assertDebuggingCalled('filetypes_util::is_whitelisted() is deprecated. ' .
 496              'Please use filetypes_util::is_listed() instead.', DEBUG_DEVELOPER);
 497      }
 498  
 499      /**
 500       * Test that a debugging noticed is displayed when calling get_not_whitelisted().
 501       */
 502      public function test_deprecation_get_not_whitelisted() {
 503  
 504          $util = new filetypes_util();
 505          $this->assertEmpty($util->get_not_whitelisted('txt', 'text/plain'));
 506          $this->assertDebuggingCalled('filetypes_util::get_not_whitelisted() is deprecated. ' .
 507              'Please use filetypes_util::get_not_listed() instead.', DEBUG_DEVELOPER);
 508      }
 509  }