Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403]

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