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