Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 38 and 311] [Versions 39 and 311]

   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   * Unit tests for /lib/filestorage/file_storage.php
  19   *
  20   * @package   core
  21   * @category  test
  22   * @copyright 2012 David Mudrak <david@moodle.com>
  23   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  namespace core;
  27  
  28  use file_exception;
  29  use file_reference_exception;
  30  use repository;
  31  use stored_file;
  32  use stored_file_creation_exception;
  33  
  34  defined('MOODLE_INTERNAL') || die();
  35  
  36  global $CFG;
  37  require_once($CFG->libdir . '/filelib.php');
  38  require_once($CFG->dirroot . '/repository/lib.php');
  39  require_once($CFG->libdir . '/filestorage/stored_file.php');
  40  
  41  /**
  42   * Unit tests for /lib/filestorage/file_storage.php
  43   *
  44   * @package   core
  45   * @category  test
  46   * @copyright 2012 David Mudrak <david@moodle.com>
  47   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  48   * @coversDefaultClass \file_storage
  49   */
  50  class file_storage_test extends \advanced_testcase {
  51  
  52      /**
  53       * Files can be created from strings.
  54       *
  55       * @covers ::create_file_from_string
  56       */
  57      public function test_create_file_from_string() {
  58          global $DB;
  59  
  60          $this->resetAfterTest(true);
  61  
  62          // Number of files installed in the database on a fresh Moodle site.
  63          $installedfiles = $DB->count_records('files', array());
  64  
  65          $content = 'abcd';
  66          $syscontext = \context_system::instance();
  67          $filerecord = array(
  68              'contextid' => $syscontext->id,
  69              'component' => 'core',
  70              'filearea'  => 'unittest',
  71              'itemid'    => 0,
  72              'filepath'  => '/images/',
  73              'filename'  => 'testfile.txt',
  74          );
  75          $pathhash = sha1('/'.$filerecord['contextid'].'/'.$filerecord['component'].'/'.$filerecord['filearea'].'/'.$filerecord['itemid'].$filerecord['filepath'].$filerecord['filename']);
  76  
  77          $fs = get_file_storage();
  78          $file = $fs->create_file_from_string($filerecord, $content);
  79  
  80          $this->assertInstanceOf('stored_file', $file);
  81          $this->assertTrue($file->compare_to_string($content));
  82          $this->assertSame($pathhash, $file->get_pathnamehash());
  83  
  84          $this->assertTrue($DB->record_exists('files', array('pathnamehash'=>$pathhash)));
  85  
  86          $filesystem = $fs->get_file_system();
  87          $location = $filesystem->get_local_path_from_storedfile($file, true);
  88  
  89          $this->assertFileExists($location);
  90  
  91          // Verify the dir placeholder files are created.
  92          $this->assertEquals($installedfiles + 3, $DB->count_records('files', array()));
  93          $this->assertTrue($DB->record_exists('files', array('pathnamehash'=>sha1('/'.$filerecord['contextid'].'/'.$filerecord['component'].'/'.$filerecord['filearea'].'/'.$filerecord['itemid'].'/.'))));
  94          $this->assertTrue($DB->record_exists('files', array('pathnamehash'=>sha1('/'.$filerecord['contextid'].'/'.$filerecord['component'].'/'.$filerecord['filearea'].'/'.$filerecord['itemid'].$filerecord['filepath'].'.'))));
  95  
  96          // Tests that missing content file is recreated.
  97  
  98          unlink($location);
  99          $this->assertFileDoesNotExist($location);
 100  
 101          $filerecord['filename'] = 'testfile2.txt';
 102          $file2 = $fs->create_file_from_string($filerecord, $content);
 103          $this->assertInstanceOf('stored_file', $file2);
 104          $this->assertSame($file->get_contenthash(), $file2->get_contenthash());
 105          $this->assertFileExists($location);
 106  
 107          $this->assertEquals($installedfiles + 4, $DB->count_records('files', array()));
 108  
 109          // Test that borked content file is recreated.
 110  
 111          $this->assertSame(2, file_put_contents($location, 'xx'));
 112  
 113          $filerecord['filename'] = 'testfile3.txt';
 114          $file3 = $fs->create_file_from_string($filerecord, $content);
 115          $this->assertInstanceOf('stored_file', $file3);
 116          $this->assertSame($file->get_contenthash(), $file3->get_contenthash());
 117          $this->assertFileExists($location);
 118  
 119          $this->assertSame($content, file_get_contents($location));
 120          $this->assertDebuggingCalled();
 121  
 122          $this->assertEquals($installedfiles + 5, $DB->count_records('files', array()));
 123      }
 124  
 125      /**
 126       * Local files can be added to the filepool
 127       *
 128       * @covers ::create_file_from_pathname
 129       */
 130      public function test_create_file_from_pathname() {
 131          global $CFG, $DB;
 132  
 133          $this->resetAfterTest(true);
 134  
 135          // Number of files installed in the database on a fresh Moodle site.
 136          $installedfiles = $DB->count_records('files', array());
 137  
 138          $filepath = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
 139          $syscontext = \context_system::instance();
 140          $filerecord = array(
 141              'contextid' => $syscontext->id,
 142              'component' => 'core',
 143              'filearea'  => 'unittest',
 144              'itemid'    => 0,
 145              'filepath'  => '/images/',
 146              'filename'  => 'testimage.jpg',
 147          );
 148          $pathhash = sha1('/'.$filerecord['contextid'].'/'.$filerecord['component'].'/'.$filerecord['filearea'].'/'.$filerecord['itemid'].$filerecord['filepath'].$filerecord['filename']);
 149  
 150          $fs = get_file_storage();
 151          $file = $fs->create_file_from_pathname($filerecord, $filepath);
 152  
 153          $this->assertInstanceOf('stored_file', $file);
 154          $this->assertTrue($file->compare_to_path($filepath));
 155  
 156          $this->assertTrue($DB->record_exists('files', array('pathnamehash'=>$pathhash)));
 157  
 158          $filesystem = $fs->get_file_system();
 159          $location = $filesystem->get_local_path_from_storedfile($file, true);
 160  
 161          $this->assertFileExists($location);
 162  
 163          // Verify the dir placeholder files are created.
 164          $this->assertEquals($installedfiles + 3, $DB->count_records('files', array()));
 165          $this->assertTrue($DB->record_exists('files', array('pathnamehash'=>sha1('/'.$filerecord['contextid'].'/'.$filerecord['component'].'/'.$filerecord['filearea'].'/'.$filerecord['itemid'].'/.'))));
 166          $this->assertTrue($DB->record_exists('files', array('pathnamehash'=>sha1('/'.$filerecord['contextid'].'/'.$filerecord['component'].'/'.$filerecord['filearea'].'/'.$filerecord['itemid'].$filerecord['filepath'].'.'))));
 167  
 168          // Tests that missing content file is recreated.
 169  
 170          unlink($location);
 171          $this->assertFileDoesNotExist($location);
 172  
 173          $filerecord['filename'] = 'testfile2.jpg';
 174          $file2 = $fs->create_file_from_pathname($filerecord, $filepath);
 175          $this->assertInstanceOf('stored_file', $file2);
 176          $this->assertSame($file->get_contenthash(), $file2->get_contenthash());
 177          $this->assertFileExists($location);
 178  
 179          $this->assertEquals($installedfiles + 4, $DB->count_records('files', array()));
 180  
 181          // Test that borked content file is recreated.
 182  
 183          $this->assertSame(2, file_put_contents($location, 'xx'));
 184  
 185          $filerecord['filename'] = 'testfile3.jpg';
 186          $file3 = $fs->create_file_from_pathname($filerecord, $filepath);
 187          $this->assertInstanceOf('stored_file', $file3);
 188          $this->assertSame($file->get_contenthash(), $file3->get_contenthash());
 189          $this->assertFileExists($location);
 190  
 191          $this->assertSame(file_get_contents($filepath), file_get_contents($location));
 192          $this->assertDebuggingCalled();
 193  
 194          $this->assertEquals($installedfiles + 5, $DB->count_records('files', array()));
 195  
 196          // Test invalid file creation.
 197  
 198          $filerecord['filename'] = 'testfile4.jpg';
 199          try {
 200              $fs->create_file_from_pathname($filerecord, $filepath.'nonexistent');
 201              $this->fail('Exception expected when trying to add non-existent stored file.');
 202          } catch (\Exception $e) {
 203              $this->assertInstanceOf('file_exception', $e);
 204          }
 205      }
 206  
 207      /**
 208       * Tests get get file.
 209       *
 210       * @covers ::get_file
 211       */
 212      public function test_get_file() {
 213          global $CFG;
 214  
 215          $this->resetAfterTest(false);
 216  
 217          $filepath = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
 218          $syscontext = \context_system::instance();
 219          $filerecord = array(
 220              'contextid' => $syscontext->id,
 221              'component' => 'core',
 222              'filearea'  => 'unittest',
 223              'itemid'    => 0,
 224              'filepath'  => '/images/',
 225              'filename'  => 'testimage.jpg',
 226          );
 227          $pathhash = sha1('/'.$filerecord['contextid'].'/'.$filerecord['component'].'/'.$filerecord['filearea'].'/'.$filerecord['itemid'].$filerecord['filepath'].$filerecord['filename']);
 228  
 229          $fs = get_file_storage();
 230          $file = $fs->create_file_from_pathname($filerecord, $filepath);
 231  
 232          $this->assertInstanceOf('stored_file', $file);
 233          $this->assertEquals($syscontext->id, $file->get_contextid());
 234          $this->assertEquals('core', $file->get_component());
 235          $this->assertEquals('unittest', $file->get_filearea());
 236          $this->assertEquals(0, $file->get_itemid());
 237          $this->assertEquals('/images/', $file->get_filepath());
 238          $this->assertEquals('testimage.jpg', $file->get_filename());
 239          $this->assertEquals(filesize($filepath), $file->get_filesize());
 240          $this->assertEquals($pathhash, $file->get_pathnamehash());
 241  
 242          return $file;
 243      }
 244  
 245      /**
 246       * Local images can be added to the filepool and their preview can be obtained
 247       *
 248       * @param stored_file $file
 249       * @depends test_get_file
 250       * @covers ::get_file_preview
 251       */
 252      public function test_get_file_preview(stored_file $file) {
 253          global $CFG;
 254  
 255          $this->resetAfterTest();
 256          $fs = get_file_storage();
 257  
 258          $previewtinyicon = $fs->get_file_preview($file, 'tinyicon');
 259          $this->assertInstanceOf('stored_file', $previewtinyicon);
 260          $this->assertEquals('6b9864ae1536a8eeef54e097319175a8be12f07c', $previewtinyicon->get_filename());
 261  
 262          $previewtinyicon = $fs->get_file_preview($file, 'thumb');
 263          $this->assertInstanceOf('stored_file', $previewtinyicon);
 264          $this->assertEquals('6b9864ae1536a8eeef54e097319175a8be12f07c', $previewtinyicon->get_filename());
 265  
 266          $this->expectException('file_exception');
 267          $fs->get_file_preview($file, 'amodewhichdoesntexist');
 268      }
 269  
 270      /**
 271       * Tests for get_file_preview without an image.
 272       *
 273       * @covers ::get_file_preview
 274       */
 275      public function test_get_file_preview_nonimage() {
 276          $this->resetAfterTest(true);
 277          $syscontext = \context_system::instance();
 278          $filerecord = array(
 279              'contextid' => $syscontext->id,
 280              'component' => 'core',
 281              'filearea'  => 'unittest',
 282              'itemid'    => 0,
 283              'filepath'  => '/textfiles/',
 284              'filename'  => 'testtext.txt',
 285          );
 286  
 287          $fs = get_file_storage();
 288          $fs->create_file_from_string($filerecord, 'text contents');
 289          $textfile = $fs->get_file($syscontext->id, $filerecord['component'], $filerecord['filearea'],
 290              $filerecord['itemid'], $filerecord['filepath'], $filerecord['filename']);
 291  
 292          $preview = $fs->get_file_preview($textfile, 'thumb');
 293          $this->assertFalse($preview);
 294      }
 295  
 296      /**
 297       * Make sure renaming is working
 298       *
 299       * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org}
 300       * @covers \stored_file::rename
 301       */
 302      public function test_file_renaming() {
 303          global $CFG;
 304  
 305          $this->resetAfterTest();
 306          $fs = get_file_storage();
 307          $syscontext = \context_system::instance();
 308          $component = 'core';
 309          $filearea  = 'unittest';
 310          $itemid    = 0;
 311          $filepath  = '/';
 312          $filename  = 'test.txt';
 313  
 314          $filerecord = array(
 315              'contextid' => $syscontext->id,
 316              'component' => $component,
 317              'filearea'  => $filearea,
 318              'itemid'    => $itemid,
 319              'filepath'  => $filepath,
 320              'filename'  => $filename,
 321          );
 322  
 323          $originalfile = $fs->create_file_from_string($filerecord, 'Test content');
 324          $this->assertInstanceOf('stored_file', $originalfile);
 325          $contenthash = $originalfile->get_contenthash();
 326          $newpath = '/test/';
 327          $newname = 'newtest.txt';
 328  
 329          // This should work.
 330          $originalfile->rename($newpath, $newname);
 331          $file = $fs->get_file($syscontext->id, $component, $filearea, $itemid, $newpath, $newname);
 332          $this->assertInstanceOf('stored_file', $file);
 333          $this->assertEquals($contenthash, $file->get_contenthash());
 334  
 335          // Try break it.
 336          $this->expectException('file_exception');
 337          $this->expectExceptionMessage('Cannot create file 1/core/unittest/0/test/newtest.txt (file exists, cannot rename)');
 338          // This shall throw exception.
 339          $originalfile->rename($newpath, $newname);
 340      }
 341  
 342      /**
 343       * Create file from reference tests
 344       *
 345       * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org}
 346       * @covers ::create_file_from_reference
 347       */
 348      public function test_create_file_from_reference() {
 349          global $CFG, $DB;
 350  
 351          $this->resetAfterTest();
 352          // Create user.
 353          $generator = $this->getDataGenerator();
 354          $user = $generator->create_user();
 355          $this->setUser($user);
 356          $usercontext = \context_user::instance($user->id);
 357          $syscontext = \context_system::instance();
 358  
 359          $fs = get_file_storage();
 360  
 361          $repositorypluginname = 'user';
 362          // Override repository permission.
 363          $capability = 'repository/' . $repositorypluginname . ':view';
 364          $guestroleid = $DB->get_field('role', 'id', array('shortname' => 'guest'));
 365          assign_capability($capability, CAP_ALLOW, $guestroleid, $syscontext->id, true);
 366  
 367          $args = array();
 368          $args['type'] = $repositorypluginname;
 369          $repos = repository::get_instances($args);
 370          $userrepository = reset($repos);
 371          $this->assertInstanceOf('repository', $userrepository);
 372  
 373          $component = 'user';
 374          $filearea  = 'private';
 375          $itemid    = 0;
 376          $filepath  = '/';
 377          $filename  = 'userfile.txt';
 378  
 379          $filerecord = array(
 380              'contextid' => $usercontext->id,
 381              'component' => $component,
 382              'filearea'  => $filearea,
 383              'itemid'    => $itemid,
 384              'filepath'  => $filepath,
 385              'filename'  => $filename,
 386          );
 387  
 388          $content = 'Test content';
 389          $originalfile = $fs->create_file_from_string($filerecord, $content);
 390          $this->assertInstanceOf('stored_file', $originalfile);
 391  
 392          $newfilerecord = array(
 393              'contextid' => $syscontext->id,
 394              'component' => 'core',
 395              'filearea'  => 'phpunit',
 396              'itemid'    => 0,
 397              'filepath'  => $filepath,
 398              'filename'  => $filename,
 399          );
 400          $ref = $fs->pack_reference($filerecord);
 401          $newstoredfile = $fs->create_file_from_reference($newfilerecord, $userrepository->id, $ref);
 402          $this->assertInstanceOf('stored_file', $newstoredfile);
 403          $this->assertEquals($userrepository->id, $newstoredfile->get_repository_id());
 404          $this->assertEquals($originalfile->get_contenthash(), $newstoredfile->get_contenthash());
 405          $this->assertEquals($originalfile->get_filesize(), $newstoredfile->get_filesize());
 406          $this->assertMatchesRegularExpression('#' . $filename. '$#', $newstoredfile->get_reference_details());
 407  
 408          // Test looking for references.
 409          $count = $fs->get_references_count_by_storedfile($originalfile);
 410          $this->assertEquals(1, $count);
 411          $files = $fs->get_references_by_storedfile($originalfile);
 412          $file = reset($files);
 413          $this->assertEquals($file, $newstoredfile);
 414  
 415          // Look for references by repository ID.
 416          $files = $fs->get_external_files($userrepository->id);
 417          $file = reset($files);
 418          $this->assertEquals($file, $newstoredfile);
 419  
 420          // Try convert reference to local file.
 421          $importedfile = $fs->import_external_file($newstoredfile);
 422          $this->assertFalse($importedfile->is_external_file());
 423          $this->assertInstanceOf('stored_file', $importedfile);
 424          // Still readable?
 425          $this->assertEquals($content, $importedfile->get_content());
 426      }
 427  
 428      /**
 429       * Create file from reference tests
 430       *
 431       * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org}
 432       * @covers ::create_file_from_reference
 433       */
 434      public function test_create_file_from_reference_with_content_hash() {
 435          global $CFG, $DB;
 436  
 437          $this->resetAfterTest();
 438          // Create user.
 439          $generator = $this->getDataGenerator();
 440          $user = $generator->create_user();
 441          $this->setUser($user);
 442          $usercontext = \context_user::instance($user->id);
 443          $syscontext = \context_system::instance();
 444  
 445          $fs = get_file_storage();
 446  
 447          $repositorypluginname = 'user';
 448          // Override repository permission.
 449          $capability = 'repository/' . $repositorypluginname . ':view';
 450          $guestroleid = $DB->get_field('role', 'id', array('shortname' => 'guest'));
 451          assign_capability($capability, CAP_ALLOW, $guestroleid, $syscontext->id, true);
 452  
 453          $args = array();
 454          $args['type'] = $repositorypluginname;
 455          $repos = repository::get_instances($args);
 456          $userrepository = reset($repos);
 457          $this->assertInstanceOf('repository', $userrepository);
 458  
 459          $component = 'user';
 460          $filearea = 'private';
 461          $itemid = 0;
 462          $filepath = '/';
 463          $filename = 'userfile.txt';
 464  
 465          $filerecord = array(
 466                  'contextid' => $usercontext->id,
 467                  'component' => $component,
 468                  'filearea' => $filearea,
 469                  'itemid' => $itemid,
 470                  'filepath' => $filepath,
 471                  'filename' => $filename,
 472          );
 473  
 474          $content = 'Test content';
 475          $originalfile = $fs->create_file_from_string($filerecord, $content);
 476          $this->assertInstanceOf('stored_file', $originalfile);
 477  
 478          $otherfilerecord = $filerecord;
 479          $otherfilerecord['filename'] = 'other-filename.txt';
 480          $otherfilewithsamecontents = $fs->create_file_from_string($otherfilerecord, $content);
 481          $this->assertInstanceOf('stored_file', $otherfilewithsamecontents);
 482  
 483          $newfilerecord = array(
 484                  'contextid' => $syscontext->id,
 485                  'component' => 'core',
 486                  'filearea' => 'phpunit',
 487                  'itemid' => 0,
 488                  'filepath' => $filepath,
 489                  'filename' => $filename,
 490                  'contenthash' => $originalfile->get_contenthash(),
 491          );
 492          $ref = $fs->pack_reference($filerecord);
 493          $newstoredfile = $fs->create_file_from_reference($newfilerecord, $userrepository->id, $ref);
 494          $this->assertInstanceOf('stored_file', $newstoredfile);
 495          $this->assertEquals($userrepository->id, $newstoredfile->get_repository_id());
 496          $this->assertEquals($originalfile->get_contenthash(), $newstoredfile->get_contenthash());
 497          $this->assertEquals($originalfile->get_filesize(), $newstoredfile->get_filesize());
 498          $this->assertMatchesRegularExpression('#' . $filename . '$#', $newstoredfile->get_reference_details());
 499      }
 500  
 501      private function setup_three_private_files() {
 502  
 503          $this->resetAfterTest();
 504  
 505          $generator = $this->getDataGenerator();
 506          $user = $generator->create_user();
 507          $this->setUser($user->id);
 508          $usercontext = \context_user::instance($user->id);
 509          // Create a user private file.
 510          $file1 = new \stdClass;
 511          $file1->contextid = $usercontext->id;
 512          $file1->component = 'user';
 513          $file1->filearea  = 'private';
 514          $file1->itemid    = 0;
 515          $file1->filepath  = '/';
 516          $file1->filename  = '1.txt';
 517          $file1->source    = 'test';
 518  
 519          $fs = get_file_storage();
 520          $userfile1 = $fs->create_file_from_string($file1, 'file1 content');
 521          $this->assertInstanceOf('stored_file', $userfile1);
 522  
 523          $file2 = clone($file1);
 524          $file2->filename = '2.txt';
 525          $userfile2 = $fs->create_file_from_string($file2, 'file2 content longer');
 526          $this->assertInstanceOf('stored_file', $userfile2);
 527  
 528          $file3 = clone($file1);
 529          $file3->filename = '3.txt';
 530          $userfile3 = $fs->create_file_from_storedfile($file3, $userfile2);
 531          $this->assertInstanceOf('stored_file', $userfile3);
 532  
 533          $user->ctxid = $usercontext->id;
 534  
 535          return $user;
 536      }
 537  
 538      /**
 539       * Tests for get_area_files
 540       *
 541       * @covers ::get_area_files
 542       */
 543      public function test_get_area_files() {
 544          $user = $this->setup_three_private_files();
 545          $fs = get_file_storage();
 546  
 547          // Get area files with default options.
 548          $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
 549  
 550          // Should be the two files we added plus the folder.
 551          $this->assertEquals(4, count($areafiles));
 552  
 553          // Verify structure.
 554          foreach ($areafiles as $key => $file) {
 555              $this->assertInstanceOf('stored_file', $file);
 556              $this->assertEquals($key, $file->get_pathnamehash());
 557          }
 558  
 559          // Get area files without a folder.
 560          $folderlessfiles = $fs->get_area_files($user->ctxid, 'user', 'private', false, 'sortorder', false);
 561          // Should be the two files without folder.
 562          $this->assertEquals(3, count($folderlessfiles));
 563  
 564          // Verify structure.
 565          foreach ($folderlessfiles as $key => $file) {
 566              $this->assertInstanceOf('stored_file', $file);
 567              $this->assertEquals($key, $file->get_pathnamehash());
 568          }
 569  
 570          // Get area files ordered by id.
 571          $filesbyid  = $fs->get_area_files($user->ctxid, 'user', 'private', false, 'id', false);
 572          // Should be the two files without folder.
 573          $this->assertEquals(3, count($filesbyid));
 574  
 575          // Verify structure.
 576          foreach ($filesbyid as $key => $file) {
 577              $this->assertInstanceOf('stored_file', $file);
 578              $this->assertEquals($key, $file->get_pathnamehash());
 579          }
 580  
 581          // Test the limit feature to retrieve each individual file.
 582          $limited = $fs->get_area_files($user->ctxid, 'user', 'private', false, 'filename', false,
 583                  0, 0, 1);
 584          $mapfunc = function($f) {
 585              return $f->get_filename();
 586          };
 587          $this->assertEquals(array('1.txt'), array_values(array_map($mapfunc, $limited)));
 588          $limited = $fs->get_area_files($user->ctxid, 'user', 'private', false, 'filename', false,
 589                  0, 1, 50);
 590          $this->assertEquals(array('2.txt', '3.txt'), array_values(array_map($mapfunc, $limited)));
 591  
 592          // Test with an itemid with no files.
 593          $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private', 666, 'sortorder', false);
 594          // Should be none.
 595          $this->assertEmpty($areafiles);
 596      }
 597  
 598      /**
 599       * Tests for get_area_tree
 600       *
 601       * @covers ::get_area_tree
 602       */
 603      public function test_get_area_tree() {
 604          $user = $this->setup_three_private_files();
 605          $fs = get_file_storage();
 606  
 607          // Get area files with default options.
 608          $areatree = $fs->get_area_tree($user->ctxid, 'user', 'private', 0);
 609          $this->assertEmpty($areatree['subdirs']);
 610          $this->assertNotEmpty($areatree['files']);
 611          $this->assertCount(3, $areatree['files']);
 612  
 613          // Ensure an empty try with a fake itemid.
 614          $emptytree = $fs->get_area_tree($user->ctxid, 'user', 'private', 666);
 615          $this->assertEmpty($emptytree['subdirs']);
 616          $this->assertEmpty($emptytree['files']);
 617  
 618          // Create a subdir.
 619          $dir = $fs->create_directory($user->ctxid, 'user', 'private', 0, '/testsubdir/');
 620          $this->assertInstanceOf('stored_file', $dir);
 621  
 622          // Add a file to the subdir.
 623          $filerecord = array(
 624              'contextid' => $user->ctxid,
 625              'component' => 'user',
 626              'filearea'  => 'private',
 627              'itemid'    => 0,
 628              'filepath'  => '/testsubdir/',
 629              'filename'  => 'test-get-area-tree.txt',
 630          );
 631  
 632          $directoryfile = $fs->create_file_from_string($filerecord, 'Test content');
 633          $this->assertInstanceOf('stored_file', $directoryfile);
 634  
 635          $areatree = $fs->get_area_tree($user->ctxid, 'user', 'private', 0);
 636  
 637          // At the top level there should still be 3 files.
 638          $this->assertCount(3, $areatree['files']);
 639  
 640          // There should now be a subdirectory.
 641          $this->assertCount(1, $areatree['subdirs']);
 642  
 643          // The test subdir is named testsubdir.
 644          $subdir = $areatree['subdirs']['testsubdir'];
 645          $this->assertNotEmpty($subdir);
 646          // It should have one file we added.
 647          $this->assertCount(1, $subdir['files']);
 648          // And no subdirs itself.
 649          $this->assertCount(0, $subdir['subdirs']);
 650  
 651          // Verify the file is the one we added.
 652          $subdirfile = reset($subdir['files']);
 653          $this->assertInstanceOf('stored_file', $subdirfile);
 654          $this->assertEquals($filerecord['filename'], $subdirfile->get_filename());
 655      }
 656  
 657      /**
 658       * Tests for get_file_by_id
 659       *
 660       * @covers ::get_file_by_id
 661       */
 662      public function test_get_file_by_id() {
 663          $user = $this->setup_three_private_files();
 664          $fs = get_file_storage();
 665  
 666          $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
 667  
 668          // Test get_file_by_id.
 669          $filebyid = reset($areafiles);
 670          $shouldbesame = $fs->get_file_by_id($filebyid->get_id());
 671          $this->assertEquals($filebyid->get_contenthash(), $shouldbesame->get_contenthash());
 672  
 673          // Test an id which doens't exist.
 674          $doesntexist = $fs->get_file_by_id(99999);
 675          $this->assertFalse($doesntexist);
 676      }
 677  
 678      /**
 679       * Tests for get_file_by_hash
 680       *
 681       * @covers ::get_file_by_hash
 682       */
 683      public function test_get_file_by_hash() {
 684          $user = $this->setup_three_private_files();
 685          $fs = get_file_storage();
 686  
 687          $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
 688          // Test get_file_by_hash.
 689          $filebyhash = reset($areafiles);
 690          $shouldbesame = $fs->get_file_by_hash($filebyhash->get_pathnamehash());
 691          $this->assertEquals($filebyhash->get_id(), $shouldbesame->get_id());
 692  
 693          // Test an hash which doens't exist.
 694          $doesntexist = $fs->get_file_by_hash('DOESNTEXIST');
 695          $this->assertFalse($doesntexist);
 696      }
 697  
 698      /**
 699       * Tests for get_external_files
 700       *
 701       * @covers ::get_external_files
 702       */
 703      public function test_get_external_files() {
 704          $user = $this->setup_three_private_files();
 705          $fs = get_file_storage();
 706  
 707          $repos = repository::get_instances(array('type'=>'user'));
 708          $userrepository = reset($repos);
 709          $this->assertInstanceOf('repository', $userrepository);
 710  
 711          // No aliases yet.
 712          $exfiles = $fs->get_external_files($userrepository->id, 'id');
 713          $this->assertEquals(array(), $exfiles);
 714  
 715          // Create three aliases linking the same original: $aliasfile1 and $aliasfile2 are
 716          // created via create_file_from_reference(), $aliasfile3 created from $aliasfile2.
 717          $originalfile = null;
 718          foreach ($fs->get_area_files($user->ctxid, 'user', 'private') as $areafile) {
 719              if (!$areafile->is_directory()) {
 720                  $originalfile = $areafile;
 721                  break;
 722              }
 723          }
 724          $this->assertInstanceOf('stored_file', $originalfile);
 725          $originalrecord = array(
 726              'contextid' => $originalfile->get_contextid(),
 727              'component' => $originalfile->get_component(),
 728              'filearea'  => $originalfile->get_filearea(),
 729              'itemid'    => $originalfile->get_itemid(),
 730              'filepath'  => $originalfile->get_filepath(),
 731              'filename'  => $originalfile->get_filename(),
 732          );
 733  
 734          $aliasrecord = $this->generate_file_record();
 735          $aliasrecord->filepath = '/foo/';
 736          $aliasrecord->filename = 'one.txt';
 737  
 738          $ref = $fs->pack_reference($originalrecord);
 739          $aliasfile1 = $fs->create_file_from_reference($aliasrecord, $userrepository->id, $ref);
 740  
 741          $aliasrecord->filepath = '/bar/';
 742          $aliasrecord->filename = 'uno.txt';
 743          // Change the order of the items in the array to make sure that it does not matter.
 744          ksort($originalrecord);
 745          $ref = $fs->pack_reference($originalrecord);
 746          $aliasfile2 = $fs->create_file_from_reference($aliasrecord, $userrepository->id, $ref);
 747  
 748          $aliasrecord->filepath = '/bar/';
 749          $aliasrecord->filename = 'jedna.txt';
 750          $aliasfile3 = $fs->create_file_from_storedfile($aliasrecord, $aliasfile2);
 751  
 752          // Make sure we get three aliases now.
 753          $exfiles = $fs->get_external_files($userrepository->id, 'id');
 754          $this->assertEquals(3, count($exfiles));
 755          foreach ($exfiles as $exfile) {
 756              $this->assertTrue($exfile->is_external_file());
 757          }
 758          // Make sure they all link the same original (thence that all are linked with the same
 759          // record in {files_reference}).
 760          $this->assertEquals($aliasfile1->get_referencefileid(), $aliasfile2->get_referencefileid());
 761          $this->assertEquals($aliasfile3->get_referencefileid(), $aliasfile2->get_referencefileid());
 762      }
 763  
 764      /**
 765       * Tests for create_directory with a negative contextid.
 766       *
 767       * @covers ::create_directory
 768       */
 769      public function test_create_directory_contextid_negative() {
 770          $fs = get_file_storage();
 771  
 772          $this->expectException('file_exception');
 773          $fs->create_directory(-1, 'core', 'unittest', 0, '/');
 774      }
 775  
 776      /**
 777       * Tests for create_directory with an invalid contextid.
 778       *
 779       * @covers ::create_directory
 780       */
 781      public function test_create_directory_contextid_invalid() {
 782          $fs = get_file_storage();
 783  
 784          $this->expectException('file_exception');
 785          $fs->create_directory('not an int', 'core', 'unittest', 0, '/');
 786      }
 787  
 788      /**
 789       * Tests for create_directory with an invalid component.
 790       *
 791       * @covers ::create_directory
 792       */
 793      public function test_create_directory_component_invalid() {
 794          $fs = get_file_storage();
 795          $syscontext = \context_system::instance();
 796  
 797          $this->expectException('file_exception');
 798          $fs->create_directory($syscontext->id, 'bad/component', 'unittest', 0, '/');
 799      }
 800  
 801      /**
 802       * Tests for create_directory with an invalid filearea.
 803       *
 804       * @covers ::create_directory
 805       */
 806      public function test_create_directory_filearea_invalid() {
 807          $fs = get_file_storage();
 808          $syscontext = \context_system::instance();
 809  
 810          $this->expectException('file_exception');
 811          $fs->create_directory($syscontext->id, 'core', 'bad-filearea', 0, '/');
 812      }
 813  
 814      /**
 815       * Tests for create_directory with a negative itemid
 816       *
 817       * @covers ::create_directory
 818       */
 819      public function test_create_directory_itemid_negative() {
 820          $fs = get_file_storage();
 821          $syscontext = \context_system::instance();
 822  
 823          $this->expectException('file_exception');
 824          $fs->create_directory($syscontext->id, 'core', 'unittest', -1, '/');
 825      }
 826  
 827      /**
 828       * Tests for create_directory with an invalid itemid
 829       *
 830       * @covers ::create_directory
 831       */
 832      public function test_create_directory_itemid_invalid() {
 833          $fs = get_file_storage();
 834          $syscontext = \context_system::instance();
 835  
 836          $this->expectException('file_exception');
 837          $fs->create_directory($syscontext->id, 'core', 'unittest', 'notanint', '/');
 838      }
 839  
 840      /**
 841       * Tests for create_directory with an invalid filepath
 842       *
 843       * @covers ::create_directory
 844       */
 845      public function test_create_directory_filepath_invalid() {
 846          $fs = get_file_storage();
 847          $syscontext = \context_system::instance();
 848  
 849          $this->expectException('file_exception');
 850          $fs->create_directory($syscontext->id, 'core', 'unittest', 0, '/not-with-trailing/or-leading-slash');
 851      }
 852  
 853      /**
 854       * Tests for get_directory_files.
 855       *
 856       * @covers ::get_directory_files
 857       */
 858      public function test_get_directory_files() {
 859          $user = $this->setup_three_private_files();
 860          $fs = get_file_storage();
 861  
 862          $dir = $fs->create_directory($user->ctxid, 'user', 'private', 0, '/testsubdir/');
 863          $this->assertInstanceOf('stored_file', $dir);
 864  
 865          // Add a file to the subdir.
 866          $filerecord = array(
 867              'contextid' => $user->ctxid,
 868              'component' => 'user',
 869              'filearea'  => 'private',
 870              'itemid'    => 0,
 871              'filepath'  => '/testsubdir/',
 872              'filename'  => 'test-get-area-tree.txt',
 873          );
 874  
 875          $directoryfile = $fs->create_file_from_string($filerecord, 'Test content');
 876          $this->assertInstanceOf('stored_file', $directoryfile);
 877  
 878          // Don't recurse without dirs.
 879          $files = $fs->get_directory_files($user->ctxid, 'user', 'private', 0, '/', false, false, 'id');
 880          // 3 files only.
 881          $this->assertCount(3, $files);
 882          foreach ($files as $key => $file) {
 883              $this->assertInstanceOf('stored_file', $file);
 884              $this->assertEquals($key, $file->get_pathnamehash());
 885          }
 886  
 887          // Don't recurse with dirs.
 888          $files = $fs->get_directory_files($user->ctxid, 'user', 'private', 0, '/', false, true, 'id');
 889          // 3 files + 1 directory.
 890          $this->assertCount(4, $files);
 891          foreach ($files as $key => $file) {
 892              $this->assertInstanceOf('stored_file', $file);
 893              $this->assertEquals($key, $file->get_pathnamehash());
 894          }
 895  
 896          // Recurse with dirs.
 897          $files = $fs->get_directory_files($user->ctxid, 'user', 'private', 0, '/', true, true, 'id');
 898          // 3 files + 1 directory +  1 subdir file.
 899          $this->assertCount(5, $files);
 900          foreach ($files as $key => $file) {
 901              $this->assertInstanceOf('stored_file', $file);
 902              $this->assertEquals($key, $file->get_pathnamehash());
 903          }
 904  
 905          // Recurse without dirs.
 906          $files = $fs->get_directory_files($user->ctxid, 'user', 'private', 0, '/', true, false, 'id');
 907          // 3 files +  1 subdir file.
 908          $this->assertCount(4, $files);
 909          foreach ($files as $key => $file) {
 910              $this->assertInstanceOf('stored_file', $file);
 911              $this->assertEquals($key, $file->get_pathnamehash());
 912          }
 913      }
 914  
 915      /**
 916       * Tests for search_references.
 917       *
 918       * @covers ::search_references
 919       */
 920      public function test_search_references() {
 921          $user = $this->setup_three_private_files();
 922          $fs = get_file_storage();
 923          $repos = repository::get_instances(array('type'=>'user'));
 924          $repo = reset($repos);
 925  
 926          $alias1 = array(
 927              'contextid' => $user->ctxid,
 928              'component' => 'user',
 929              'filearea'  => 'private',
 930              'itemid'    => 0,
 931              'filepath'  => '/aliases/',
 932              'filename'  => 'alias-to-1.txt'
 933          );
 934  
 935          $alias2 = array(
 936              'contextid' => $user->ctxid,
 937              'component' => 'user',
 938              'filearea'  => 'private',
 939              'itemid'    => 0,
 940              'filepath'  => '/aliases/',
 941              'filename'  => 'another-alias-to-1.txt'
 942          );
 943  
 944          $reference = \file_storage::pack_reference(array(
 945              'contextid' => $user->ctxid,
 946              'component' => 'user',
 947              'filearea'  => 'private',
 948              'itemid'    => 0,
 949              'filepath'  => '/',
 950              'filename'  => '1.txt'
 951          ));
 952  
 953          // There are no aliases now.
 954          $result = $fs->search_references($reference);
 955          $this->assertEquals(array(), $result);
 956  
 957          $result = $fs->search_references_count($reference);
 958          $this->assertSame($result, 0);
 959  
 960          // Create two aliases and make sure they are returned.
 961          $fs->create_file_from_reference($alias1, $repo->id, $reference);
 962          $fs->create_file_from_reference($alias2, $repo->id, $reference);
 963  
 964          $result = $fs->search_references($reference);
 965          $this->assertTrue(is_array($result));
 966          $this->assertEquals(count($result), 2);
 967          foreach ($result as $alias) {
 968              $this->assertTrue($alias instanceof stored_file);
 969          }
 970  
 971          $result = $fs->search_references_count($reference);
 972          $this->assertSame($result, 2);
 973  
 974          // The method can't be used for references to files outside the filepool.
 975          $exceptionthrown = false;
 976          try {
 977              $fs->search_references('http://dl.dropbox.com/download/1234567/naked-dougiamas.jpg');
 978          } catch (file_reference_exception $e) {
 979              $exceptionthrown = true;
 980          }
 981          $this->assertTrue($exceptionthrown);
 982  
 983          $exceptionthrown = false;
 984          try {
 985              $fs->search_references_count('http://dl.dropbox.com/download/1234567/naked-dougiamas.jpg');
 986          } catch (file_reference_exception $e) {
 987              $exceptionthrown = true;
 988          }
 989          $this->assertTrue($exceptionthrown);
 990      }
 991  
 992      /**
 993       * Tests for delete_area_files.
 994       *
 995       * @covers ::delete_area_files
 996       */
 997      public function test_delete_area_files() {
 998          $user = $this->setup_three_private_files();
 999          $fs = get_file_storage();
1000  
1001          // Get area files with default options.
1002          $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
1003          // Should be the two files we added plus the folder.
1004          $this->assertEquals(4, count($areafiles));
1005          $fs->delete_area_files($user->ctxid, 'user', 'private');
1006  
1007          $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
1008          // Should be the two files we added plus the folder.
1009          $this->assertEquals(0, count($areafiles));
1010      }
1011  
1012      /**
1013       * Tests for delete_area_files using an itemid.
1014       *
1015       * @covers ::delete_area_files
1016       */
1017      public function test_delete_area_files_itemid() {
1018          $user = $this->setup_three_private_files();
1019          $fs = get_file_storage();
1020  
1021          // Get area files with default options.
1022          $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
1023          // Should be the two files we added plus the folder.
1024          $this->assertEquals(4, count($areafiles));
1025          $fs->delete_area_files($user->ctxid, 'user', 'private', 9999);
1026  
1027          $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
1028          $this->assertEquals(4, count($areafiles));
1029      }
1030  
1031      /**
1032       * Tests for delete_area_files_select.
1033       *
1034       * @covers ::delete_area_files_select
1035       */
1036      public function test_delete_area_files_select() {
1037          $user = $this->setup_three_private_files();
1038          $fs = get_file_storage();
1039  
1040          // Get area files with default options.
1041          $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
1042          // Should be the two files we added plus the folder.
1043          $this->assertEquals(4, count($areafiles));
1044          $fs->delete_area_files_select($user->ctxid, 'user', 'private', '!= :notitemid', array('notitemid'=>9999));
1045  
1046          $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
1047          // Should be the two files we added plus the folder.
1048          $this->assertEquals(0, count($areafiles));
1049      }
1050  
1051      /**
1052       * Tests for delete_component_files.
1053       *
1054       * @covers ::delete_component_files
1055       */
1056      public function test_delete_component_files() {
1057          $user = $this->setup_three_private_files();
1058          $fs = get_file_storage();
1059  
1060          $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
1061          $this->assertEquals(4, count($areafiles));
1062          $fs->delete_component_files('user');
1063          $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
1064          $this->assertEquals(0, count($areafiles));
1065      }
1066  
1067      /**
1068       * Tests for create_file_from_url.
1069       *
1070       * @covers ::create_file_from_url
1071       */
1072      public function test_create_file_from_url() {
1073          $this->resetAfterTest(true);
1074  
1075          $syscontext = \context_system::instance();
1076          $filerecord = array(
1077              'contextid' => $syscontext->id,
1078              'component' => 'core',
1079              'filearea'  => 'unittest',
1080              'itemid'    => 0,
1081              'filepath'  => '/downloadtest/',
1082          );
1083          $url = $this->getExternalTestFileUrl('/test.html');
1084  
1085          $fs = get_file_storage();
1086  
1087          // Test creating file without filename.
1088          $file1 = $fs->create_file_from_url($filerecord, $url);
1089          $this->assertInstanceOf('stored_file', $file1);
1090  
1091          // Set filename.
1092          $filerecord['filename'] = 'unit-test-filename.html';
1093          $file2 = $fs->create_file_from_url($filerecord, $url);
1094          $this->assertInstanceOf('stored_file', $file2);
1095  
1096          // Use temporary file.
1097          $filerecord['filename'] = 'unit-test-with-temp-file.html';
1098          $file3 = $fs->create_file_from_url($filerecord, $url, null, true);
1099          $file3 = $this->assertInstanceOf('stored_file', $file3);
1100      }
1101  
1102      /**
1103       * Tests for cron.
1104       *
1105       * @covers ::cron
1106       */
1107      public function test_cron() {
1108          $this->resetAfterTest(true);
1109  
1110          // Note: this is only testing DB compatibility atm, rather than
1111          // that work is done.
1112          $fs = get_file_storage();
1113  
1114          $this->expectOutputRegex('/Cleaning up/');
1115          $fs->cron();
1116      }
1117  
1118      /**
1119       * Tests for is_area_empty.
1120       *
1121       * @covers ::is_area_empty
1122       */
1123      public function test_is_area_empty() {
1124          $user = $this->setup_three_private_files();
1125          $fs = get_file_storage();
1126  
1127          $this->assertFalse($fs->is_area_empty($user->ctxid, 'user', 'private'));
1128  
1129          // File area with madeup itemid should be empty.
1130          $this->assertTrue($fs->is_area_empty($user->ctxid, 'user', 'private', 9999));
1131          // Still empty with dirs included.
1132          $this->assertTrue($fs->is_area_empty($user->ctxid, 'user', 'private', 9999, false));
1133      }
1134  
1135      /**
1136       * Tests for move_area_files_to_new_context.
1137       *
1138       * @covers ::move_area_files_to_new_context
1139       */
1140      public function test_move_area_files_to_new_context() {
1141          $this->resetAfterTest(true);
1142  
1143          // Create a course with a page resource.
1144          $course = $this->getDataGenerator()->create_course();
1145          $page1 = $this->getDataGenerator()->create_module('page', array('course'=>$course->id));
1146          $page1context = \context_module::instance($page1->cmid);
1147  
1148          // Add a file to the page.
1149          $fs = get_file_storage();
1150          $filerecord = array(
1151              'contextid' => $page1context->id,
1152              'component' => 'mod_page',
1153              'filearea'  => 'content',
1154              'itemid'    => 0,
1155              'filepath'  => '/',
1156              'filename'  => 'unit-test-file.txt',
1157          );
1158  
1159          $originalfile = $fs->create_file_from_string($filerecord, 'Test content');
1160          $this->assertInstanceOf('stored_file', $originalfile);
1161  
1162          $pagefiles = $fs->get_area_files($page1context->id, 'mod_page', 'content', 0, 'sortorder', false);
1163          // Should be one file in filearea.
1164          $this->assertFalse($fs->is_area_empty($page1context->id, 'mod_page', 'content'));
1165  
1166          // Create a new page.
1167          $page2 = $this->getDataGenerator()->create_module('page', array('course'=>$course->id));
1168          $page2context = \context_module::instance($page2->cmid);
1169  
1170          // Newly created page area is empty.
1171          $this->assertTrue($fs->is_area_empty($page2context->id, 'mod_page', 'content'));
1172  
1173          // Move the files.
1174          $fs->move_area_files_to_new_context($page1context->id, $page2context->id, 'mod_page', 'content');
1175  
1176          // Page2 filearea should no longer be empty.
1177          $this->assertFalse($fs->is_area_empty($page2context->id, 'mod_page', 'content'));
1178  
1179          // Page1 filearea should now be empty.
1180          $this->assertTrue($fs->is_area_empty($page1context->id, 'mod_page', 'content'));
1181  
1182          $page2files = $fs->get_area_files($page2context->id, 'mod_page', 'content', 0, 'sortorder', false);
1183          $movedfile = reset($page2files);
1184  
1185          // The two files should have the same content hash.
1186          $this->assertEquals($movedfile->get_contenthash(), $originalfile->get_contenthash());
1187      }
1188  
1189      /**
1190       * Tests for convert_image.
1191       *
1192       * @covers ::convert_image
1193       */
1194      public function test_convert_image() {
1195          global $CFG;
1196  
1197          $this->resetAfterTest(false);
1198  
1199          $filepath = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1200          $syscontext = \context_system::instance();
1201          $filerecord = array(
1202              'contextid' => $syscontext->id,
1203              'component' => 'core',
1204              'filearea'  => 'unittest',
1205              'itemid'    => 0,
1206              'filepath'  => '/images/',
1207              'filename'  => 'testimage.jpg',
1208          );
1209  
1210          $fs = get_file_storage();
1211          $original = $fs->create_file_from_pathname($filerecord, $filepath);
1212  
1213          $filerecord['filename'] = 'testimage-converted-10x10.jpg';
1214          $converted = $fs->convert_image($filerecord, $original, 10, 10, true, 100);
1215          $this->assertInstanceOf('stored_file', $converted);
1216  
1217          $filerecord['filename'] = 'testimage-convereted-nosize.jpg';
1218          $converted = $fs->convert_image($filerecord, $original);
1219          $this->assertInstanceOf('stored_file', $converted);
1220      }
1221  
1222      /**
1223       * Tests for convert_image with a PNG.
1224       *
1225       * @covers ::convert_image
1226       */
1227      public function test_convert_image_png() {
1228          global $CFG;
1229  
1230          $this->resetAfterTest(false);
1231  
1232          $filepath = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.png';
1233          $syscontext = \context_system::instance();
1234          $filerecord = array(
1235              'contextid' => $syscontext->id,
1236              'component' => 'core',
1237              'filearea'  => 'unittest',
1238              'itemid'    => 0,
1239              'filepath'  => '/images/',
1240              'filename'  => 'testimage.png',
1241          );
1242  
1243          $fs = get_file_storage();
1244          $original = $fs->create_file_from_pathname($filerecord, $filepath);
1245  
1246          // Vanilla test.
1247          $filerecord['filename'] = 'testimage-converted-nosize.png';
1248          $vanilla = $fs->convert_image($filerecord, $original);
1249          $this->assertInstanceOf('stored_file', $vanilla);
1250          // Assert that byte 25 has the ascii value 6 for PNG-24.
1251          $this->assertTrue(ord(substr($vanilla->get_content(), 25, 1)) == 6);
1252  
1253          // 10x10 resize test; also testing for a ridiculous quality setting, which
1254          // we should if necessary scale to the 0 - 9 range.
1255          $filerecord['filename'] = 'testimage-converted-10x10.png';
1256          $converted = $fs->convert_image($filerecord, $original, 10, 10, true, 100);
1257          $this->assertInstanceOf('stored_file', $converted);
1258          // Assert that byte 25 has the ascii value 6 for PNG-24.
1259          $this->assertTrue(ord(substr($converted->get_content(), 25, 1)) == 6);
1260  
1261          // Transparency test.
1262          $filerecord['filename'] = 'testimage-converted-102x31.png';
1263          $converted = $fs->convert_image($filerecord, $original, 102, 31, true, 9);
1264          $this->assertInstanceOf('stored_file', $converted);
1265          // Assert that byte 25 has the ascii value 6 for PNG-24.
1266          $this->assertTrue(ord(substr($converted->get_content(), 25, 1)) == 6);
1267  
1268          $originalfile = imagecreatefromstring($original->get_content());
1269          $convertedfile = imagecreatefromstring($converted->get_content());
1270          $vanillafile = imagecreatefromstring($vanilla->get_content());
1271  
1272          $originalcolors = imagecolorsforindex($originalfile, imagecolorat($originalfile, 0, 0));
1273          $convertedcolors = imagecolorsforindex($convertedfile, imagecolorat($convertedfile, 0, 0));
1274          $vanillacolors = imagecolorsforindex($vanillafile, imagecolorat($vanillafile, 0, 0));
1275          $this->assertEquals(count($originalcolors), 4);
1276          $this->assertEquals(count($convertedcolors), 4);
1277          $this->assertEquals(count($vanillacolors), 4);
1278          $this->assertEquals($originalcolors['red'], $convertedcolors['red']);
1279          $this->assertEquals($originalcolors['green'], $convertedcolors['green']);
1280          $this->assertEquals($originalcolors['blue'], $convertedcolors['blue']);
1281          $this->assertEquals($originalcolors['alpha'], $convertedcolors['alpha']);
1282          $this->assertEquals($originalcolors['red'], $vanillacolors['red']);
1283          $this->assertEquals($originalcolors['green'], $vanillacolors['green']);
1284          $this->assertEquals($originalcolors['blue'], $vanillacolors['blue']);
1285          $this->assertEquals($originalcolors['alpha'], $vanillacolors['alpha']);
1286          $this->assertEquals($originalcolors['alpha'], 127);
1287  
1288      }
1289  
1290      private function generate_file_record() {
1291          $syscontext = \context_system::instance();
1292          $filerecord = new \stdClass();
1293          $filerecord->contextid = $syscontext->id;
1294          $filerecord->component = 'core';
1295          $filerecord->filearea = 'phpunit';
1296          $filerecord->filepath = '/';
1297          $filerecord->filename = 'testfile.txt';
1298          $filerecord->itemid = 0;
1299  
1300          return $filerecord;
1301      }
1302  
1303      /**
1304       * @covers ::create_file_from_storedfile
1305       */
1306      public function test_create_file_from_storedfile_file_invalid() {
1307          $this->resetAfterTest(true);
1308  
1309          $filerecord = $this->generate_file_record();
1310  
1311          $fs = get_file_storage();
1312  
1313          // Create a file from a file id which doesn't exist.
1314          $this->expectException(file_exception::class);
1315          $fs->create_file_from_storedfile($filerecord,  9999);
1316      }
1317  
1318      /**
1319       * @covers ::create_file_from_storedfile
1320       */
1321      public function test_create_file_from_storedfile_contextid_invalid() {
1322          $this->resetAfterTest(true);
1323  
1324          $filerecord = $this->generate_file_record();
1325  
1326          $fs = get_file_storage();
1327          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1328          $this->assertInstanceOf('stored_file', $file1);
1329  
1330          $filerecord->filename = 'invalid.txt';
1331          $filerecord->contextid = 'invalid';
1332  
1333          $this->expectException(file_exception::class);
1334          $this->expectExceptionMessage('Invalid contextid');
1335          $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1336      }
1337  
1338      /**
1339       * @covers ::create_file_from_storedfile
1340       */
1341      public function test_create_file_from_storedfile_component_invalid() {
1342          $this->resetAfterTest(true);
1343  
1344          $filerecord = $this->generate_file_record();
1345  
1346          $fs = get_file_storage();
1347          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1348          $this->assertInstanceOf('stored_file', $file1);
1349  
1350          $filerecord->filename = 'invalid.txt';
1351          $filerecord->component = 'bad/component';
1352  
1353          $this->expectException(file_exception::class);
1354          $this->expectExceptionMessage('Invalid component');
1355          $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1356      }
1357  
1358      /**
1359       * @covers ::create_file_from_storedfile
1360       */
1361      public function test_create_file_from_storedfile_filearea_invalid() {
1362          $this->resetAfterTest(true);
1363  
1364          $filerecord = $this->generate_file_record();
1365  
1366          $fs = get_file_storage();
1367          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1368          $this->assertInstanceOf('stored_file', $file1);
1369  
1370          $filerecord->filename = 'invalid.txt';
1371          $filerecord->filearea = 'bad-filearea';
1372  
1373          $this->expectException(file_exception::class);
1374          $this->expectExceptionMessage('Invalid filearea');
1375          $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1376      }
1377  
1378      /**
1379       * @covers ::create_file_from_storedfile
1380       */
1381      public function test_create_file_from_storedfile_itemid_invalid() {
1382          $this->resetAfterTest(true);
1383  
1384          $filerecord = $this->generate_file_record();
1385  
1386          $fs = get_file_storage();
1387          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1388          $this->assertInstanceOf('stored_file', $file1);
1389  
1390          $filerecord->filename = 'invalid.txt';
1391          $filerecord->itemid = 'bad-itemid';
1392  
1393          $this->expectException(file_exception::class);
1394          $this->expectExceptionMessage('Invalid itemid');
1395          $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1396      }
1397  
1398      /**
1399       * @covers ::create_file_from_storedfile
1400       */
1401      public function test_create_file_from_storedfile_filepath_invalid() {
1402          $this->resetAfterTest(true);
1403  
1404          $filerecord = $this->generate_file_record();
1405  
1406          $fs = get_file_storage();
1407          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1408          $this->assertInstanceOf('stored_file', $file1);
1409  
1410          $filerecord->filename = 'invalid.txt';
1411          $filerecord->filepath = 'a-/bad/-filepath';
1412  
1413          $this->expectException(file_exception::class);
1414          $this->expectExceptionMessage('Invalid file path');
1415          $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1416      }
1417  
1418      /**
1419       * @covers ::create_file_from_storedfile
1420       */
1421      public function test_create_file_from_storedfile_filename_invalid() {
1422          $this->resetAfterTest(true);
1423  
1424          $filerecord = $this->generate_file_record();
1425  
1426          $fs = get_file_storage();
1427          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1428          $this->assertInstanceOf('stored_file', $file1);
1429  
1430          $filerecord->filename = '';
1431  
1432          $this->expectException(file_exception::class);
1433          $this->expectExceptionMessage('Invalid file name');
1434          $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1435      }
1436  
1437      /**
1438       * @covers ::create_file_from_storedfile
1439       */
1440      public function test_create_file_from_storedfile_timecreated_invalid() {
1441          $this->resetAfterTest(true);
1442  
1443          $filerecord = $this->generate_file_record();
1444  
1445          $fs = get_file_storage();
1446          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1447          $this->assertInstanceOf('stored_file', $file1);
1448  
1449          $filerecord->filename = 'invalid.txt';
1450          $filerecord->timecreated = 'today';
1451  
1452          $this->expectException(file_exception::class);
1453          $this->expectExceptionMessage('Invalid file timecreated');
1454          $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1455      }
1456  
1457      /**
1458       * @covers ::create_file_from_storedfile
1459       */
1460      public function test_create_file_from_storedfile_timemodified_invalid() {
1461          $this->resetAfterTest(true);
1462  
1463          $filerecord = $this->generate_file_record();
1464  
1465          $fs = get_file_storage();
1466          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1467          $this->assertInstanceOf('stored_file', $file1);
1468  
1469          $filerecord->filename = 'invalid.txt';
1470          $filerecord->timemodified  = 'today';
1471  
1472          $this->expectException(file_exception::class);
1473          $this->expectExceptionMessage('Invalid file timemodified');
1474          $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1475      }
1476  
1477      /**
1478       * @covers ::create_file_from_storedfile
1479       */
1480      public function test_create_file_from_storedfile_duplicate() {
1481          $this->resetAfterTest(true);
1482  
1483          $filerecord = $this->generate_file_record();
1484  
1485          $fs = get_file_storage();
1486          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1487          $this->assertInstanceOf('stored_file', $file1);
1488  
1489          // Creating a file validating unique constraint.
1490          $this->expectException(stored_file_creation_exception::class);
1491          $this->expectExceptionMessage('Cannot create file 1/core/phpunit/0/testfile.txt');
1492          $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1493      }
1494  
1495      /**
1496       * Tests for create_file_from_storedfile.
1497       *
1498       * @covers ::create_file_from_storedfile
1499       */
1500      public function test_create_file_from_storedfile() {
1501          $this->resetAfterTest(true);
1502  
1503          $syscontext = \context_system::instance();
1504  
1505          $filerecord = new \stdClass();
1506          $filerecord->contextid = $syscontext->id;
1507          $filerecord->component = 'core';
1508          $filerecord->filearea = 'phpunit';
1509          $filerecord->filepath = '/';
1510          $filerecord->filename = 'testfile.txt';
1511          $filerecord->itemid = 0;
1512  
1513          $fs = get_file_storage();
1514  
1515          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1516          $this->assertInstanceOf('stored_file', $file1);
1517  
1518          $filerecord->filename = 'test-create-file-from-storedfile.txt';
1519          $file2 = $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1520          $this->assertInstanceOf('stored_file', $file2);
1521  
1522          // These will be normalised to current time..
1523          $filerecord->timecreated = -100;
1524          $filerecord->timemodified= -100;
1525          $filerecord->filename = 'test-create-file-from-storedfile-bad-dates.txt';
1526  
1527          $file3 = $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1528          $this->assertInstanceOf('stored_file', $file3);
1529  
1530          $this->assertNotEquals($file3->get_timemodified(), $filerecord->timemodified);
1531          $this->assertNotEquals($file3->get_timecreated(), $filerecord->timecreated);
1532      }
1533  
1534      /**
1535       * @covers ::create_file_from_string
1536       */
1537      public function test_create_file_from_string_contextid_invalid() {
1538          $this->resetAfterTest(true);
1539  
1540          $filerecord = $this->generate_file_record();
1541          $fs = get_file_storage();
1542  
1543          $filerecord->contextid = 'invalid';
1544  
1545          $this->expectException(file_exception::class);
1546          $this->expectExceptionMessage('Invalid contextid');
1547          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1548      }
1549  
1550      /**
1551       * @covers ::create_file_from_string
1552       */
1553      public function test_create_file_from_string_component_invalid() {
1554          $this->resetAfterTest(true);
1555  
1556          $filerecord = $this->generate_file_record();
1557          $fs = get_file_storage();
1558  
1559          $filerecord->component = 'bad/component';
1560  
1561          $this->expectException(file_exception::class);
1562          $this->expectExceptionMessage('Invalid component');
1563          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1564      }
1565  
1566      /**
1567       * @covers ::create_file_from_string
1568       */
1569      public function test_create_file_from_string_filearea_invalid() {
1570          $this->resetAfterTest(true);
1571  
1572          $filerecord = $this->generate_file_record();
1573          $fs = get_file_storage();
1574  
1575          $filerecord->filearea = 'bad-filearea';
1576  
1577          $this->expectException(file_exception::class);
1578          $this->expectExceptionMessage('Invalid filearea');
1579          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1580      }
1581  
1582      /**
1583       * @covers ::create_file_from_string
1584       */
1585      public function test_create_file_from_string_itemid_invalid() {
1586          $this->resetAfterTest(true);
1587  
1588          $filerecord = $this->generate_file_record();
1589          $fs = get_file_storage();
1590  
1591          $filerecord->itemid = 'bad-itemid';
1592  
1593          $this->expectException(file_exception::class);
1594          $this->expectExceptionMessage('Invalid itemid');
1595          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1596      }
1597  
1598      /**
1599       * @covers ::create_file_from_string
1600       */
1601      public function test_create_file_from_string_filepath_invalid() {
1602          $this->resetAfterTest(true);
1603  
1604          $filerecord = $this->generate_file_record();
1605          $fs = get_file_storage();
1606  
1607          $filerecord->filepath = 'a-/bad/-filepath';
1608  
1609          $this->expectException(file_exception::class);
1610          $this->expectExceptionMessage('Invalid file path');
1611          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1612      }
1613  
1614      /**
1615       * @covers ::create_file_from_string
1616       */
1617      public function test_create_file_from_string_filename_invalid() {
1618          $this->resetAfterTest(true);
1619  
1620          $filerecord = $this->generate_file_record();
1621          $fs = get_file_storage();
1622  
1623          $filerecord->filename = '';
1624  
1625          $this->expectException(file_exception::class);
1626          $this->expectExceptionMessage('Invalid file name');
1627          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1628      }
1629  
1630      /**
1631       * @covers ::create_file_from_string
1632       */
1633      public function test_create_file_from_string_timecreated_invalid() {
1634          $this->resetAfterTest(true);
1635  
1636          $filerecord = $this->generate_file_record();
1637          $fs = get_file_storage();
1638  
1639          $filerecord->timecreated = 'today';
1640  
1641          $this->expectException('file_exception');
1642          $this->expectExceptionMessage('Invalid file timecreated');
1643          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1644      }
1645  
1646      /**
1647       * @covers ::create_file_from_string
1648       */
1649      public function test_create_file_from_string_timemodified_invalid() {
1650          $this->resetAfterTest(true);
1651  
1652          $filerecord = $this->generate_file_record();
1653          $fs = get_file_storage();
1654  
1655          $filerecord->timemodified  = 'today';
1656  
1657          $this->expectException(file_exception::class);
1658          $this->expectExceptionMessage('Invalid file timemodified');
1659          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1660      }
1661  
1662      /**
1663       * Tests for create_file_from_string with a duplicate string.
1664       * @covers ::create_file_from_string
1665       */
1666      public function test_create_file_from_string_duplicate() {
1667          $this->resetAfterTest(true);
1668  
1669          $filerecord = $this->generate_file_record();
1670          $fs = get_file_storage();
1671  
1672          $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1673  
1674          // Creating a file validating unique constraint.
1675          $this->expectException('stored_file_creation_exception');
1676          $file2 = $fs->create_file_from_string($filerecord, 'text contents');
1677      }
1678  
1679      /**
1680       * @covers ::create_file_from_pathname
1681       */
1682      public function test_create_file_from_pathname_contextid_invalid() {
1683          global $CFG;
1684          $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1685  
1686          $this->resetAfterTest(true);
1687  
1688          $filerecord = $this->generate_file_record();
1689          $fs = get_file_storage();
1690  
1691          $filerecord->contextid = 'invalid';
1692  
1693          $this->expectException(file_exception::class);
1694          $this->expectExceptionMessage('Invalid contextid');
1695          $file1 = $fs->create_file_from_pathname($filerecord, $path);
1696      }
1697  
1698      /**
1699       * @covers ::create_file_from_pathname
1700       */
1701      public function test_create_file_from_pathname_component_invalid() {
1702          global $CFG;
1703          $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1704  
1705          $this->resetAfterTest(true);
1706  
1707          $filerecord = $this->generate_file_record();
1708          $fs = get_file_storage();
1709  
1710          $filerecord->component = 'bad/component';
1711  
1712          $this->expectException(file_exception::class);
1713          $this->expectExceptionMessage('Invalid component');
1714          $file1 = $fs->create_file_from_pathname($filerecord, $path);
1715      }
1716  
1717      /**
1718       * @covers ::create_file_from_pathname
1719       */
1720      public function test_create_file_from_pathname_filearea_invalid() {
1721          global $CFG;
1722          $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1723  
1724          $this->resetAfterTest(true);
1725  
1726          $filerecord = $this->generate_file_record();
1727          $fs = get_file_storage();
1728  
1729          $filerecord->filearea = 'bad-filearea';
1730  
1731          $this->expectException(file_exception::class);
1732          $this->expectExceptionMessage('Invalid filearea');
1733          $file1 = $fs->create_file_from_pathname($filerecord, $path);
1734      }
1735  
1736      /**
1737       * @covers ::create_file_from_pathname
1738       */
1739      public function test_create_file_from_pathname_itemid_invalid() {
1740          global $CFG;
1741          $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1742  
1743          $this->resetAfterTest(true);
1744  
1745          $filerecord = $this->generate_file_record();
1746          $fs = get_file_storage();
1747  
1748          $filerecord->itemid = 'bad-itemid';
1749  
1750          $this->expectException(file_exception::class);
1751          $this->expectExceptionMessage('Invalid itemid');
1752          $file1 = $fs->create_file_from_pathname($filerecord, $path);
1753      }
1754  
1755      /**
1756       * @covers ::create_file_from_pathname
1757       */
1758      public function test_create_file_from_pathname_filepath_invalid() {
1759          global $CFG;
1760          $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1761  
1762          $this->resetAfterTest(true);
1763  
1764          $filerecord = $this->generate_file_record();
1765          $fs = get_file_storage();
1766  
1767          $filerecord->filepath = 'a-/bad/-filepath';
1768  
1769          $this->expectException(file_exception::class);
1770          $this->expectExceptionMessage('Invalid file path');
1771          $file1 = $fs->create_file_from_pathname($filerecord, $path);
1772      }
1773  
1774      /**
1775       * @covers ::create_file_from_pathname
1776       */
1777      public function test_create_file_from_pathname_filename_invalid() {
1778          global $CFG;
1779          $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1780  
1781          $this->resetAfterTest(true);
1782  
1783          $filerecord = $this->generate_file_record();
1784          $fs = get_file_storage();
1785  
1786          $filerecord->filename = '';
1787  
1788          $this->expectException(file_exception::class);
1789          $this->expectExceptionMessage('Invalid file name');
1790          $file1 = $fs->create_file_from_pathname($filerecord, $path);
1791      }
1792  
1793      /**
1794       * @covers ::create_file_from_pathname
1795       */
1796      public function test_create_file_from_pathname_timecreated_invalid() {
1797          global $CFG;
1798          $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1799  
1800          $this->resetAfterTest(true);
1801  
1802          $filerecord = $this->generate_file_record();
1803          $fs = get_file_storage();
1804  
1805          $filerecord->timecreated = 'today';
1806  
1807          $this->expectException(file_exception::class);
1808          $this->expectExceptionMessage('Invalid file timecreated');
1809          $file1 = $fs->create_file_from_pathname($filerecord, $path);
1810      }
1811  
1812      /**
1813       * @covers ::create_file_from_pathname
1814       */
1815      public function test_create_file_from_pathname_timemodified_invalid() {
1816          global $CFG;
1817          $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1818  
1819          $this->resetAfterTest(true);
1820  
1821          $filerecord = $this->generate_file_record();
1822          $fs = get_file_storage();
1823  
1824          $filerecord->timemodified  = 'today';
1825  
1826          $this->expectException(file_exception::class);
1827          $this->expectExceptionMessage('Invalid file timemodified');
1828          $file1 = $fs->create_file_from_pathname($filerecord, $path);
1829      }
1830  
1831      /**
1832       * @covers ::create_file_from_pathname
1833       */
1834      public function test_create_file_from_pathname_duplicate_file() {
1835          global $CFG;
1836          $this->resetAfterTest(true);
1837  
1838          $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1839  
1840          $filerecord = $this->generate_file_record();
1841          $fs = get_file_storage();
1842  
1843          $file1 = $fs->create_file_from_pathname($filerecord, $path);
1844          $this->assertInstanceOf('stored_file', $file1);
1845  
1846          // Creating a file validating unique constraint.
1847          $this->expectException(stored_file_creation_exception::class);
1848          $this->expectExceptionMessage('Cannot create file 1/core/phpunit/0/testfile.txt');
1849          $file2 = $fs->create_file_from_pathname($filerecord, $path);
1850      }
1851  
1852      /**
1853       * Calling \stored_file::delete_reference() on a non-reference file throws coding_exception
1854       *
1855       * @covers \stored_file::delete_reference
1856       */
1857      public function test_delete_reference_on_nonreference() {
1858  
1859          $this->resetAfterTest(true);
1860          $user = $this->setup_three_private_files();
1861          $fs = get_file_storage();
1862          $repos = repository::get_instances(array('type'=>'user'));
1863          $repo = reset($repos);
1864  
1865          $file = null;
1866          foreach ($fs->get_area_files($user->ctxid, 'user', 'private') as $areafile) {
1867              if (!$areafile->is_directory()) {
1868                  $file = $areafile;
1869                  break;
1870              }
1871          }
1872          $this->assertInstanceOf('stored_file', $file);
1873          $this->assertFalse($file->is_external_file());
1874  
1875          $this->expectException('coding_exception');
1876          $file->delete_reference();
1877      }
1878  
1879      /**
1880       * Calling \stored_file::delete_reference() on a reference file does not affect other
1881       * symlinks to the same original
1882       *
1883       * @covers \stored_file::delete_reference
1884       */
1885      public function test_delete_reference_one_symlink_does_not_rule_them_all() {
1886  
1887          $this->resetAfterTest(true);
1888          $user = $this->setup_three_private_files();
1889          $fs = get_file_storage();
1890          $repos = repository::get_instances(array('type'=>'user'));
1891          $repo = reset($repos);
1892  
1893          // Create two aliases linking the same original.
1894  
1895          $originalfile = null;
1896          foreach ($fs->get_area_files($user->ctxid, 'user', 'private') as $areafile) {
1897              if (!$areafile->is_directory()) {
1898                  $originalfile = $areafile;
1899                  break;
1900              }
1901          }
1902          $this->assertInstanceOf('stored_file', $originalfile);
1903  
1904          // Calling delete_reference() on a non-reference file.
1905  
1906          $originalrecord = array(
1907              'contextid' => $originalfile->get_contextid(),
1908              'component' => $originalfile->get_component(),
1909              'filearea'  => $originalfile->get_filearea(),
1910              'itemid'    => $originalfile->get_itemid(),
1911              'filepath'  => $originalfile->get_filepath(),
1912              'filename'  => $originalfile->get_filename(),
1913          );
1914  
1915          $aliasrecord = $this->generate_file_record();
1916          $aliasrecord->filepath = '/A/';
1917          $aliasrecord->filename = 'symlink.txt';
1918  
1919          $ref = $fs->pack_reference($originalrecord);
1920          $aliasfile1 = $fs->create_file_from_reference($aliasrecord, $repo->id, $ref);
1921  
1922          $aliasrecord->filepath = '/B/';
1923          $aliasrecord->filename = 'symlink.txt';
1924          $ref = $fs->pack_reference($originalrecord);
1925          $aliasfile2 = $fs->create_file_from_reference($aliasrecord, $repo->id, $ref);
1926  
1927          // Refetch A/symlink.txt file.
1928          $symlink1 = $fs->get_file($aliasrecord->contextid, $aliasrecord->component,
1929              $aliasrecord->filearea, $aliasrecord->itemid, '/A/', 'symlink.txt');
1930          $this->assertTrue($symlink1->is_external_file());
1931  
1932          // Unlink the A/symlink.txt file.
1933          $symlink1->delete_reference();
1934          $this->assertFalse($symlink1->is_external_file());
1935  
1936          // Make sure that B/symlink.txt has not been affected.
1937          $symlink2 = $fs->get_file($aliasrecord->contextid, $aliasrecord->component,
1938              $aliasrecord->filearea, $aliasrecord->itemid, '/B/', 'symlink.txt');
1939          $this->assertTrue($symlink2->is_external_file());
1940      }
1941  
1942      /**
1943       * Make sure that when internal file is updated all references to it are
1944       * updated immediately. When it is deleted, the references are converted
1945       * to true copies.
1946       */
1947      public function test_update_reference_internal() {
1948          purge_all_caches();
1949          $this->resetAfterTest(true);
1950          $user = $this->setup_three_private_files();
1951          $fs = get_file_storage();
1952          $repos = repository::get_instances(array('type' => 'user'));
1953          $repo = reset($repos);
1954  
1955          // Create two aliases linking the same original.
1956  
1957          $areafiles = array_values($fs->get_area_files($user->ctxid, 'user', 'private', false, 'filename', false));
1958  
1959          $originalfile = $areafiles[0];
1960          $this->assertInstanceOf('stored_file', $originalfile);
1961          $contenthash = $originalfile->get_contenthash();
1962          $filesize = $originalfile->get_filesize();
1963  
1964          $substitutefile = $areafiles[1];
1965          $this->assertInstanceOf('stored_file', $substitutefile);
1966          $newcontenthash = $substitutefile->get_contenthash();
1967          $newfilesize = $substitutefile->get_filesize();
1968  
1969          $originalrecord = array(
1970              'contextid' => $originalfile->get_contextid(),
1971              'component' => $originalfile->get_component(),
1972              'filearea'  => $originalfile->get_filearea(),
1973              'itemid'    => $originalfile->get_itemid(),
1974              'filepath'  => $originalfile->get_filepath(),
1975              'filename'  => $originalfile->get_filename(),
1976          );
1977  
1978          $aliasrecord = $this->generate_file_record();
1979          $aliasrecord->filepath = '/A/';
1980          $aliasrecord->filename = 'symlink.txt';
1981  
1982          $ref = $fs->pack_reference($originalrecord);
1983          $symlink1 = $fs->create_file_from_reference($aliasrecord, $repo->id, $ref);
1984          // Make sure created alias is a reference and has the same size and contenthash as source.
1985          $this->assertEquals($contenthash, $symlink1->get_contenthash());
1986          $this->assertEquals($filesize, $symlink1->get_filesize());
1987          $this->assertEquals($repo->id, $symlink1->get_repository_id());
1988          $this->assertNotEmpty($symlink1->get_referencefileid());
1989          $referenceid = $symlink1->get_referencefileid();
1990  
1991          $aliasrecord->filepath = '/B/';
1992          $aliasrecord->filename = 'symlink.txt';
1993          $ref = $fs->pack_reference($originalrecord);
1994          $symlink2 = $fs->create_file_from_reference($aliasrecord, $repo->id, $ref);
1995          // Make sure created alias is a reference and has the same size and contenthash as source.
1996          $this->assertEquals($contenthash, $symlink2->get_contenthash());
1997          $this->assertEquals($filesize, $symlink2->get_filesize());
1998          $this->assertEquals($repo->id, $symlink2->get_repository_id());
1999          // Make sure both aliases have the same reference id.
2000          $this->assertEquals($referenceid, $symlink2->get_referencefileid());
2001  
2002          // Overwrite ofiginal file.
2003          $originalfile->replace_file_with($substitutefile);
2004          $this->assertEquals($newcontenthash, $originalfile->get_contenthash());
2005          $this->assertEquals($newfilesize, $originalfile->get_filesize());
2006  
2007          // References to the internal files must be synchronised immediately.
2008          // Refetch A/symlink.txt file.
2009          $symlink1 = $fs->get_file($aliasrecord->contextid, $aliasrecord->component,
2010              $aliasrecord->filearea, $aliasrecord->itemid, '/A/', 'symlink.txt');
2011          $this->assertTrue($symlink1->is_external_file());
2012          $this->assertEquals($newcontenthash, $symlink1->get_contenthash());
2013          $this->assertEquals($newfilesize, $symlink1->get_filesize());
2014          $this->assertEquals($repo->id, $symlink1->get_repository_id());
2015          $this->assertEquals($referenceid, $symlink1->get_referencefileid());
2016  
2017          // Refetch B/symlink.txt file.
2018          $symlink2 = $fs->get_file($aliasrecord->contextid, $aliasrecord->component,
2019              $aliasrecord->filearea, $aliasrecord->itemid, '/B/', 'symlink.txt');
2020          $this->assertTrue($symlink2->is_external_file());
2021          $this->assertEquals($newcontenthash, $symlink2->get_contenthash());
2022          $this->assertEquals($newfilesize, $symlink2->get_filesize());
2023          $this->assertEquals($repo->id, $symlink2->get_repository_id());
2024          $this->assertEquals($referenceid, $symlink2->get_referencefileid());
2025  
2026          // Remove original file.
2027          $originalfile->delete();
2028  
2029          // References must be converted to independend files.
2030          // Refetch A/symlink.txt file.
2031          $symlink1 = $fs->get_file($aliasrecord->contextid, $aliasrecord->component,
2032              $aliasrecord->filearea, $aliasrecord->itemid, '/A/', 'symlink.txt');
2033          $this->assertFalse($symlink1->is_external_file());
2034          $this->assertEquals($newcontenthash, $symlink1->get_contenthash());
2035          $this->assertEquals($newfilesize, $symlink1->get_filesize());
2036          $this->assertNull($symlink1->get_repository_id());
2037          $this->assertNull($symlink1->get_referencefileid());
2038  
2039          // Refetch B/symlink.txt file.
2040          $symlink2 = $fs->get_file($aliasrecord->contextid, $aliasrecord->component,
2041              $aliasrecord->filearea, $aliasrecord->itemid, '/B/', 'symlink.txt');
2042          $this->assertFalse($symlink2->is_external_file());
2043          $this->assertEquals($newcontenthash, $symlink2->get_contenthash());
2044          $this->assertEquals($newfilesize, $symlink2->get_filesize());
2045          $this->assertNull($symlink2->get_repository_id());
2046          $this->assertNull($symlink2->get_referencefileid());
2047      }
2048  
2049      /**
2050       * Tests for get_unused_filename.
2051       *
2052       * @covers ::get_unused_filename
2053       */
2054      public function test_get_unused_filename() {
2055          global $USER;
2056          $this->resetAfterTest(true);
2057  
2058          $fs = get_file_storage();
2059          $this->setAdminUser();
2060          $contextid = \context_user::instance($USER->id)->id;
2061          $component = 'user';
2062          $filearea = 'private';
2063          $itemid = 0;
2064          $filepath = '/';
2065  
2066          // Create some private files.
2067          $file = new \stdClass;
2068          $file->contextid = $contextid;
2069          $file->component = 'user';
2070          $file->filearea  = 'private';
2071          $file->itemid    = 0;
2072          $file->filepath  = '/';
2073          $file->source    = 'test';
2074          $filenames = array('foo.txt', 'foo (1).txt', 'foo (20).txt', 'foo (999)', 'bar.jpg', 'What (a cool file).jpg',
2075                  'Hurray! (1).php', 'Hurray! (2).php', 'Hurray! (9a).php', 'Hurray! (abc).php');
2076          foreach ($filenames as $key => $filename) {
2077              $file->filename = $filename;
2078              $userfile = $fs->create_file_from_string($file, "file $key $filename content");
2079              $this->assertInstanceOf('stored_file', $userfile);
2080          }
2081  
2082          // Asserting new generated names.
2083          $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'unused.txt');
2084          $this->assertEquals('unused.txt', $newfilename);
2085          $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo.txt');
2086          $this->assertEquals('foo (21).txt', $newfilename);
2087          $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo (1).txt');
2088          $this->assertEquals('foo (21).txt', $newfilename);
2089          $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo (2).txt');
2090          $this->assertEquals('foo (2).txt', $newfilename);
2091          $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo (20).txt');
2092          $this->assertEquals('foo (21).txt', $newfilename);
2093          $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo');
2094          $this->assertEquals('foo', $newfilename);
2095          $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo (123)');
2096          $this->assertEquals('foo (123)', $newfilename);
2097          $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo (999)');
2098          $this->assertEquals('foo (1000)', $newfilename);
2099          $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'bar.png');
2100          $this->assertEquals('bar.png', $newfilename);
2101          $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'bar (12).png');
2102          $this->assertEquals('bar (12).png', $newfilename);
2103          $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'bar.jpg');
2104          $this->assertEquals('bar (1).jpg', $newfilename);
2105          $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'bar (1).jpg');
2106          $this->assertEquals('bar (1).jpg', $newfilename);
2107          $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'What (a cool file).jpg');
2108          $this->assertEquals('What (a cool file) (1).jpg', $newfilename);
2109          $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'Hurray! (1).php');
2110          $this->assertEquals('Hurray! (3).php', $newfilename);
2111  
2112          $this->expectException('coding_exception');
2113          $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, '');
2114      }
2115  
2116      /**
2117       * Test that mimetype_from_file returns appropriate output when the
2118       * file could not be found.
2119       *
2120       * @covers ::mimetype
2121       */
2122      public function test_mimetype_not_found() {
2123          $mimetype = \file_storage::mimetype('/path/to/nonexistent/file');
2124          $this->assertEquals('document/unknown', $mimetype);
2125      }
2126  
2127      /**
2128       * Data provider to return fixture files and their expected mimetype
2129       *
2130       * @return array[]
2131       */
2132      public function filepath_mimetype_provider(): array {
2133          return [
2134              [__DIR__ . '/fixtures/testimage.jpg', 'image/jpeg'],
2135              [__DIR__ . '/fixtures/testimage.svg', 'image/svg+xml'],
2136              [__DIR__ . '/fixtures/testimage_basic.svg', 'image/svg+xml'],
2137          ];
2138      }
2139  
2140      /**
2141       * Test that mimetype returns appropriate output for a known file.
2142       *
2143       * Note: this is not intended to check that functions outside of this
2144       * file works. It is intended to validate the codepath contains no
2145       * errors and behaves as expected.
2146       *
2147       * @covers ::mimetype
2148       *
2149       * @param string $filepath
2150       * @param string $expectedmimetype
2151       *
2152       * @dataProvider filepath_mimetype_provider
2153       */
2154      public function test_mimetype_known(string $filepath, string $expectedmimetype): void {
2155          $mimetype = \file_storage::mimetype($filepath);
2156          $this->assertEquals($expectedmimetype, $mimetype);
2157      }
2158  
2159      /**
2160       * Test that mimetype_from_file returns appropriate output when the
2161       * file could not be found.
2162       *
2163       * @covers ::mimetype_from_file
2164       */
2165      public function test_mimetype_from_file_not_found() {
2166          $mimetype = \file_storage::mimetype_from_file('/path/to/nonexistent/file');
2167          $this->assertEquals('document/unknown', $mimetype);
2168      }
2169  
2170      /**
2171       * Test that mimetype_from_file returns appropriate output for a known
2172       * file.
2173       *
2174       * Note: this is not intended to check that functions outside of this
2175       * file works. It is intended to validate the codepath contains no
2176       * errors and behaves as expected.
2177       *
2178       * @covers ::mimetype_from_file
2179       *
2180       * @param string $filepath
2181       * @param string $expectedmimetype
2182       *
2183       * @dataProvider filepath_mimetype_provider
2184       */
2185      public function test_mimetype_from_file_known(string $filepath, string $expectedmimetype): void {
2186          $mimetype = \file_storage::mimetype_from_file($filepath);
2187          $this->assertEquals($expectedmimetype, $mimetype);
2188      }
2189  
2190  }
2191  
2192  class test_stored_file_inspection extends stored_file {
2193      public static function get_pretected_pathname(stored_file $file) {
2194          return $file->get_pathname_by_contenthash();
2195      }
2196  }