Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

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