Search moodle.org's
Developer Documentation

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