Completed
Pull Request — master (#6442)
by Robbie
09:26
created

FileTest::testIni2Bytes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Assets\Tests;
4
5
use SilverStripe\Assets\Image;
6
use SilverStripe\Assets\Storage\AssetStore;
7
use SilverStripe\Assets\Tests\FileTest\MyCustomFile;
8
use SilverStripe\ORM\ValidationException;
9
use SilverStripe\ORM\Versioning\Versioned;
10
use SilverStripe\ORM\DataObject;
11
use SilverStripe\Security\Member;
12
use SilverStripe\CMS\Model\ErrorPage;
13
use SilverStripe\Assets\Filesystem;
14
use SilverStripe\Assets\Folder;
15
use SilverStripe\Assets\File;
16
use SilverStripe\Core\Config\Config;
17
use SilverStripe\Core\Injector\Injector;
18
use SilverStripe\Dev\SapphireTest;
19
use SilverStripe\Control\Session;
20
use SilverStripe\Control\Director;
21
use SilverStripe\View\Parsers\ShortcodeParser;
22
use SilverStripe\Assets\Tests\Storage\AssetStoreTest\TestAssetStore;
23
24
/**
25
 * Tests for the File class
26
 */
27
class FileTest extends SapphireTest
28
{
29
30
    protected static $fixture_file = 'FileTest.yml';
31
32
    protected $extraDataObjects = array(
33
        MyCustomFile::class
34
    );
35
36
    public function setUp()
37
    {
38
        parent::setUp();
39
        $this->logInWithPermission('ADMIN');
40
        Versioned::set_stage(Versioned::DRAFT);
41
42
        // Set backend root to /ImageTest
43
        TestAssetStore::activate('FileTest');
44
45
        // Create a test folders for each of the fixture references
46
        $folderIDs = $this->allFixtureIDs(Folder::class);
47
        foreach ($folderIDs as $folderID) {
0 ignored issues
show
Bug introduced by
The expression $folderIDs of type array|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
48
            $folder = DataObject::get_by_id(Folder::class, $folderID);
49
            $filePath = ASSETS_PATH . '/FileTest/' . $folder->getFilename();
50
            Filesystem::makeFolder($filePath);
51
        }
52
53
        // Create a test files for each of the fixture references
54
        $fileIDs = $this->allFixtureIDs(File::class);
55
        foreach ($fileIDs as $fileID) {
0 ignored issues
show
Bug introduced by
The expression $fileIDs of type array|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
56
            /**
57
             * @var File $file
58
             */
59
            $file = DataObject::get_by_id(File::class, $fileID);
60
            $root = ASSETS_PATH . '/FileTest/';
61
            if ($folder = $file->Parent()) {
62
                $root .= $folder->getFilename();
63
            }
64
            $path = $root . substr($file->getHash(), 0, 10) . '/' . basename($file->getFilename());
65
            Filesystem::makeFolder(dirname($path));
66
            $fh = fopen($path, "w+");
67
            fwrite($fh, str_repeat('x', 1000000));
68
            fclose($fh);
69
        }
70
71
        // Conditional fixture creation in case the 'cms' module is installed
72
        if (class_exists('SilverStripe\\CMS\\Model\\ErrorPage')) {
73
            $page = new ErrorPage(
74
                array(
75
                'Title' => 'Page not Found',
76
                'ErrorCode' => 404
77
                )
78
            );
79
            $page->write();
80
            $page->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
81
        }
82
    }
83
84
    public function tearDown()
85
    {
86
        TestAssetStore::reset();
87
        parent::tearDown();
88
    }
89
90
    public function testLinkShortcodeHandler()
91
    {
92
        $testFile = $this->objFromFixture(File::class, 'asdf');
93
94
        $parser = new ShortcodeParser();
95
        $parser->register('file_link', array(File::class, 'handle_shortcode'));
96
97
        $fileShortcode = sprintf('[file_link,id=%d]', $testFile->ID);
98
        $fileEnclosed  = sprintf('[file_link,id=%d]Example Content[/file_link]', $testFile->ID);
99
100
        $fileShortcodeExpected = $testFile->Link();
101
        $fileEnclosedExpected  = sprintf(
102
            '<a href="%s" class="file" data-type="txt" data-size="977 KB">Example Content</a>',
103
            $testFile->Link()
104
        );
105
106
        $this->assertEquals($fileShortcodeExpected, $parser->parse($fileShortcode), 'Test that simple linking works.');
107
        $this->assertEquals($fileEnclosedExpected, $parser->parse($fileEnclosed), 'Test enclosed content is linked.');
108
109
        $testFile->delete();
110
111
        $fileShortcode = '[file_link,id="-1"]';
112
        $fileEnclosed  = '[file_link,id="-1"]Example Content[/file_link]';
113
114
        $this->assertEquals('', $parser->parse('[file_link]'), 'Test that invalid ID attributes are not parsed.');
115
        $this->assertEquals('', $parser->parse('[file_link,id="text"]'));
116
        $this->assertEquals('', $parser->parse('[file_link]Example Content[/file_link]'));
117
118
        if (class_exists('SilverStripe\\CMS\\Model\\ErrorPage')) {
119
            $errorPage = ErrorPage::get()->filter('ErrorCode', 404)->first();
120
            $this->assertEquals(
121
                $errorPage->Link(),
122
                $parser->parse($fileShortcode),
123
                'Test link to 404 page if no suitable matches.'
124
            );
125
            $this->assertEquals(
126
                sprintf('<a href="%s">Example Content</a>', $errorPage->Link()),
127
                $parser->parse($fileEnclosed)
128
            );
129
        } else {
130
            $this->assertEquals(
131
                '',
132
                $parser->parse($fileShortcode),
133
                'Short code is removed if file record is not present.'
134
            );
135
            $this->assertEquals('', $parser->parse($fileEnclosed));
136
        }
137
    }
138
139
    public function testCreateWithFilenameWithSubfolder()
140
    {
141
        // Note: We can't use fixtures/setUp() for this, as we want to create the db record manually.
142
        // Creating the folder is necessary to avoid having "Filename" overwritten by setName()/setRelativePath(),
143
        // because the parent folders don't exist in the database
144
        $folder = Folder::find_or_make('/FileTest/');
145
        $testfilePath = BASE_PATH . '/assets/FileTest/CreateWithFilenameHasCorrectPath.txt'; // Important: No leading slash
146
        $fh = fopen($testfilePath, 'w');
147
        fwrite($fh, str_repeat('x', 1000000));
148
        fclose($fh);
149
150
        $file = new File();
151
        $file->setFromLocalFile($testfilePath);
152
        $file->ParentID = $folder->ID;
153
        $file->write();
154
155
        $this->assertEquals(
156
            'CreateWithFilenameHasCorrectPath.txt',
157
            $file->Name,
158
            '"Name" property is automatically set from "Filename"'
159
        );
160
        $this->assertEquals(
161
            'FileTest/CreateWithFilenameHasCorrectPath.txt',
162
            $file->Filename,
163
            '"Filename" property remains unchanged'
164
        );
165
166
        // TODO This should be auto-detected, see File->updateFilesystem()
167
        // $this->assertInstanceOf('Folder', $file->Parent(), 'Parent folder is created in database');
0 ignored issues
show
Unused Code Comprehensibility introduced by
73% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
168
        // $this->assertFileExists($file->Parent()->getURL(), 'Parent folder is created on filesystem');
0 ignored issues
show
Unused Code Comprehensibility introduced by
74% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
169
        // $this->assertEquals('FileTest', $file->Parent()->Name);
0 ignored issues
show
Unused Code Comprehensibility introduced by
71% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
170
        // $this->assertInstanceOf('Folder', $file->Parent()->Parent(), 'Grandparent folder is created in database');
0 ignored issues
show
Unused Code Comprehensibility introduced by
73% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
171
        // $this->assertFileExists($file->Parent()->Parent()->getURL(),
0 ignored issues
show
Unused Code Comprehensibility introduced by
74% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
172
        // 'Grandparent folder is created on filesystem');
173
        // $this->assertEquals('assets', $file->Parent()->Parent()->Name);
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
174
    }
175
176
    public function testGetExtension()
177
    {
178
        $this->assertEquals(
179
            '',
180
            File::get_file_extension('myfile'),
181
            'No extension'
182
        );
183
        $this->assertEquals(
184
            'txt',
185
            File::get_file_extension('myfile.txt'),
186
            'Simple extension'
187
        );
188
        $this->assertEquals(
189
            'gz',
190
            File::get_file_extension('myfile.tar.gz'),
191
            'Double-barrelled extension only returns last bit'
192
        );
193
    }
194
195
    public function testValidateExtension()
196
    {
197
        Session::set('loggedInAs', null);
198
199
        $orig = Config::inst()->get(File::class, 'allowed_extensions');
200
        Config::inst()->remove(File::class, 'allowed_extensions');
201
        Config::inst()->update(File::class, 'allowed_extensions', array('txt'));
202
203
        $file = $this->objFromFixture(File::class, 'asdf');
204
205
        // Invalid ext
206
        $file->Name = 'asdf.php';
0 ignored issues
show
Documentation introduced by
The property Name does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
207
        $result = $file->validate();
208
        $this->assertFalse($result->isValid());
209
        $messages = $result->getMessages();
210
        $this->assertEquals(1, count($messages));
211
        $this->assertEquals('Extension is not allowed', $messages[0]['message']);
212
213
        // Valid ext
214
        $file->Name = 'asdf.txt';
0 ignored issues
show
Documentation introduced by
The property Name does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
215
        $result = $file->validate();
216
        $this->assertTrue($result->isValid());
217
218
        // Capital extension is valid as well
219
        $file->Name = 'asdf.TXT';
0 ignored issues
show
Documentation introduced by
The property Name does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
220
        $result = $file->validate();
221
        $this->assertTrue($result->isValid());
222
    }
223
224
    public function testAppCategory()
225
    {
226
        // Test various categories
227
        $this->assertEquals('image', File::get_app_category('jpg'));
228
        $this->assertEquals('image', File::get_app_category('JPG'));
229
        $this->assertEquals('image', File::get_app_category('JPEG'));
230
        $this->assertEquals('image', File::get_app_category('png'));
231
        $this->assertEquals('image', File::get_app_category('tif'));
232
        $this->assertEquals('document', File::get_app_category('pdf'));
233
        $this->assertEquals('video', File::get_app_category('mov'));
234
        $this->assertEquals('audio', File::get_app_category('OGG'));
235
    }
236
237
    public function testGetCategoryExtensions()
238
    {
239
        // Test specific categories
240
        $images = array(
241
            'alpha', 'als', 'bmp', 'cel', 'gif', 'ico', 'icon', 'jpeg', 'jpg', 'pcx', 'png', 'ps', 'tif', 'tiff'
242
        );
243
        $this->assertEquals($images, File::get_category_extensions('image'));
244
        $this->assertEquals(array('gif', 'jpeg', 'jpg', 'png'), File::get_category_extensions('image/supported'));
245
        $this->assertEquals($images, File::get_category_extensions(array('image', 'image/supported')));
246
        $this->assertEquals(
247
            array('fla', 'gif', 'jpeg', 'jpg', 'png', 'swf'),
248
            File::get_category_extensions(array('flash', 'image/supported'))
249
        );
250
251
        // Test other categories have at least one item
252
        $this->assertNotEmpty(File::get_category_extensions('archive'));
253
        $this->assertNotEmpty(File::get_category_extensions('audio'));
254
        $this->assertNotEmpty(File::get_category_extensions('document'));
255
        $this->assertNotEmpty(File::get_category_extensions('flash'));
256
        $this->assertNotEmpty(File::get_category_extensions('video'));
257
    }
258
259
    /**
260
     * @dataProvider allowedExtensions
261
     * @param string $extension
262
     */
263
    public function testAllFilesHaveCategory($extension)
264
    {
265
        $this->assertNotEmpty(
266
            File::get_app_category($extension),
267
            "Assert that extension {$extension} has a valid category"
268
        );
269
    }
270
271
    /**
272
     * Gets the list of all extensions for testing
273
     *
274
     * @return array
275
     */
276
    public function allowedExtensions()
277
    {
278
        $args = array();
279
        foreach (array_filter(File::config()->allowed_extensions) as $ext) {
280
            $args[] = array($ext);
281
        }
282
        return $args;
283
    }
284
285
    public function testSetNameChangesFilesystemOnWrite()
286
    {
287
        /**
288
         * @var File $file
289
         */
290
        $file = $this->objFromFixture(File::class, 'asdf');
291
        $this->logInWithPermission('ADMIN');
292
        $file->publishRecursive();
293
        $oldTuple = $file->File->getValue();
294
295
        // Rename
296
        $file->Name = 'renamed.txt';
297
        $newTuple = $oldTuple;
298
        $newTuple['Filename'] = $file->generateFilename();
299
300
        // Before write()
301
        $this->assertTrue(
302
            $this->getAssetStore()->exists($oldTuple['Filename'], $oldTuple['Hash']),
303
            'Old path is still present'
304
        );
305
        $this->assertFalse(
306
            $this->getAssetStore()->exists($newTuple['Filename'], $newTuple['Hash']),
307
            'New path is updated in memory, not written before write() is called'
308
        );
309
310
        // After write()
311
        $file->write();
312
        $this->assertTrue(
313
            $this->getAssetStore()->exists($oldTuple['Filename'], $oldTuple['Hash']),
314
            'Old path exists after draft change'
315
        );
316
        $this->assertTrue(
317
            $this->getAssetStore()->exists($newTuple['Filename'], $newTuple['Hash']),
318
            'New path is created after write()'
319
        );
320
321
        // After publish
322
        $file->publishRecursive();
323
        $this->assertFalse(
324
            $this->getAssetStore()->exists($oldTuple['Filename'], $oldTuple['Hash']),
325
            'Old file is finally removed after publishing new file'
326
        );
327
        $this->assertTrue(
328
            $this->getAssetStore()->exists($newTuple['Filename'], $newTuple['Hash']),
329
            'New path is created after write()'
330
        );
331
    }
332
333
    public function testSetParentIDChangesFilesystemOnWrite()
334
    {
335
        $file = $this->objFromFixture(File::class, 'asdf');
336
        $this->logInWithPermission('ADMIN');
337
        $file->publishRecursive();
338
        $subfolder = $this->objFromFixture(Folder::class, 'subfolder');
339
        $oldTuple = $file->File->getValue();
340
341
        // set ParentID
342
        $file->ParentID = $subfolder->ID;
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
343
        $newTuple = $oldTuple;
344
        $newTuple['Filename'] = $file->generateFilename();
345
346
        // Before write()
347
        $this->assertTrue(
348
            $this->getAssetStore()->exists($oldTuple['Filename'], $oldTuple['Hash']),
349
            'Old path is still present'
350
        );
351
        $this->assertFalse(
352
            $this->getAssetStore()->exists($newTuple['Filename'], $newTuple['Hash']),
353
            'New path is updated in memory, not written before write() is called'
354
        );
355
        $file->write();
356
357
        // After write()
358
        $file->write();
359
        $this->assertTrue(
360
            $this->getAssetStore()->exists($oldTuple['Filename'], $oldTuple['Hash']),
361
            'Old path exists after draft change'
362
        );
363
        $this->assertTrue(
364
            $this->getAssetStore()->exists($newTuple['Filename'], $newTuple['Hash']),
365
            'New path is created after write()'
366
        );
367
368
        // After publish
369
        $file->publishSingle();
370
        $this->assertFalse(
371
            $this->getAssetStore()->exists($oldTuple['Filename'], $oldTuple['Hash']),
372
            'Old file is finally removed after publishing new file'
373
        );
374
        $this->assertTrue(
375
            $this->getAssetStore()->exists($newTuple['Filename'], $newTuple['Hash']),
376
            'New path is created after write()'
377
        );
378
    }
379
380
    /**
381
     * @see http://open.silverstripe.org/ticket/5693
382
     */
383
    public function testSetNameWithInvalidExtensionDoesntChangeFilesystem()
384
    {
385
        Config::inst()->remove(File::class, 'allowed_extensions');
386
        Config::inst()->update(File::class, 'allowed_extensions', array('txt'));
387
        $this->setExpectedException(ValidationException::class);
388
389
        $file = $this->objFromFixture(File::class, 'asdf');
390
        $file->Name = 'renamed.php'; // evil extension
0 ignored issues
show
Documentation introduced by
The property Name does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
391
        $file->write();
392
    }
393
394
    public function testGetURL()
395
    {
396
        $rootfile = $this->objFromFixture(File::class, 'asdf');
397
        $this->assertEquals('/assets/FileTest/55b443b601/FileTest.txt', $rootfile->getURL());
398
    }
399
400
    public function testGetAbsoluteURL()
401
    {
402
        $rootfile = $this->objFromFixture(File::class, 'asdf');
403
        $this->assertEquals(
404
            Director::absoluteBaseURL() . 'assets/FileTest/55b443b601/FileTest.txt',
405
            $rootfile->getAbsoluteURL()
406
        );
407
    }
408
409
    public function testNameAndTitleGeneration()
410
    {
411
        // When name is assigned, title is automatically assigned
412
        $file = $this->objFromFixture(Image::class, 'setfromname');
413
        $this->assertEquals('FileTest', $file->Title);
414
    }
415
416
    public function testSizeAndAbsoluteSizeParameters()
417
    {
418
        $file = $this->objFromFixture(File::class, 'asdf');
419
420
        /* AbsoluteSize will give the integer number */
421
        $this->assertEquals(1000000, $file->AbsoluteSize);
422
        /* Size will give a humanised number */
423
        $this->assertEquals('977 KB', $file->Size);
424
    }
425
426
    public function testFileType()
427
    {
428
        $file = $this->objFromFixture(Image::class, 'gif');
429
        $this->assertEquals("GIF image - good for diagrams", $file->FileType);
430
431
        $file = $this->objFromFixture(File::class, 'pdf');
432
        $this->assertEquals("Adobe Acrobat PDF file", $file->FileType);
433
434
        $file = $this->objFromFixture(Image::class, 'gifupper');
435
        $this->assertEquals("GIF image - good for diagrams", $file->FileType);
436
437
        /* Only a few file types are given special descriptions; the rest are unknown */
438
        $file = $this->objFromFixture(File::class, 'asdf');
439
        $this->assertEquals("unknown", $file->FileType);
440
    }
441
442
    /**
443
     * Test the File::format_size() method
444
     */
445
    public function testFormatSize()
446
    {
447
        $this->assertEquals("1000 bytes", File::format_size(1000));
448
        $this->assertEquals("1023 bytes", File::format_size(1023));
449
        $this->assertEquals("1 KB", File::format_size(1025));
450
        $this->assertEquals("9.8 KB", File::format_size(10000));
451
        $this->assertEquals("49 KB", File::format_size(50000));
452
        $this->assertEquals("977 KB", File::format_size(1000000));
453
        $this->assertEquals("1 MB", File::format_size(1024*1024));
454
        $this->assertEquals("954 MB", File::format_size(1000000000));
455
        $this->assertEquals("1 GB", File::format_size(1024*1024*1024));
456
        $this->assertEquals("9.3 GB", File::format_size(10000000000));
457
        // It use any denomination higher than GB.  It also doesn't overflow with >32 bit integers
458
        $this->assertEquals("93132.3 GB", File::format_size(100000000000000));
459
    }
460
461
    public function testDeleteFile()
462
    {
463
        /**
464
         * @var File $file
465
         */
466
        $file = $this->objFromFixture(File::class, 'asdf');
467
        $this->logInWithPermission('ADMIN');
468
        $file->publishSingle();
469
        $tuple = $file->File->getValue();
470
471
        // Before delete
472
        $this->assertTrue(
473
            $this->getAssetStore()->exists($tuple['Filename'], $tuple['Hash']),
474
            'File is still present'
475
        );
476
477
        // after unpublish
478
        $file->doUnpublish();
479
        $this->assertTrue(
480
            $this->getAssetStore()->exists($tuple['Filename'], $tuple['Hash']),
481
            'File is still present after unpublish'
482
        );
483
484
        // after delete
485
        $file->delete();
486
        $this->assertFalse(
487
            $this->getAssetStore()->exists($tuple['Filename'], $tuple['Hash']),
488
            'File is deleted after unpublish and delete'
489
        );
490
    }
491
492
    public function testRenameFolder()
493
    {
494
        $newTitle = "FileTest-folder-renamed";
495
496
        //rename a folder's title
497
        $folderID = $this->objFromFixture(Folder::class, "folder2")->ID;
498
        $folder = DataObject::get_by_id(Folder::class, $folderID);
499
        $folder->Title = $newTitle;
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
500
        $folder->write();
501
502
        //get folder again and see if the filename has changed
503
        $folder = DataObject::get_by_id(Folder::class, $folderID);
504
        $this->assertEquals(
505
            $newTitle . '/',
506
            $folder->Filename,
507
            "Folder Filename updated after rename of Title"
508
        );
509
510
        //rename a folder's name
511
        $newTitle2 = "FileTest-folder-renamed2";
512
        $folder->Name = $newTitle2;
0 ignored issues
show
Documentation introduced by
The property Name does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
513
        $folder->write();
514
515
        //get folder again and see if the Title has changed
516
        $folder = DataObject::get_by_id(Folder::class, $folderID);
517
        $this->assertEquals(
518
            $folder->Title,
519
            $newTitle2,
520
            "Folder Title updated after rename of Name"
521
        );
522
523
524
        //rename a folder's Filename
525
        $newTitle3 = "FileTest-folder-renamed3";
526
        $folder->Filename = $newTitle3;
0 ignored issues
show
Documentation introduced by
The property Filename does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
527
        $folder->write();
528
529
        //get folder again and see if the Title has changed
530
        $folder = DataObject::get_by_id(Folder::class, $folderID);
531
        $this->assertEquals(
532
            $folder->Title,
533
            $newTitle3,
534
            "Folder Title updated after rename of Filename"
535
        );
536
    }
537
538
    public function testSetsOwnerOnFirstWrite()
539
    {
540
        Session::set('loggedInAs', null);
541
        $member1 = new Member();
542
        $member1->write();
543
        $member2 = new Member();
544
        $member2->write();
545
546
        $file1 = new File();
547
        $file1->write();
548
        $this->assertEquals(0, $file1->OwnerID, 'Owner not written when no user is logged in');
549
550
        $member1->logIn();
551
        $file2 = new File();
552
        $file2->write();
553
        $this->assertEquals($member1->ID, $file2->OwnerID, 'Owner written when user is logged in');
554
555
        $member2->logIn();
556
        $file2->forceChange();
557
        $file2->write();
558
        $this->assertEquals($member1->ID, $file2->OwnerID, 'Owner not overwritten on existing files');
559
    }
560
561
    public function testCanEdit()
562
    {
563
        $file = $this->objFromFixture(Image::class, 'gif');
564
565
        // Test anonymous permissions
566
        Session::set('loggedInAs', null);
567
        $this->assertFalse($file->canEdit(), "Anonymous users can't edit files");
568
569
        // Test permissionless user
570
        $this->objFromFixture(Member::class, 'frontend')->logIn();
571
        $this->assertFalse($file->canEdit(), "Permissionless users can't edit files");
572
573
        // Test global CMS section users
574
        $this->objFromFixture(Member::class, 'cms')->logIn();
575
        $this->assertTrue($file->canEdit(), "Users with all CMS section access can edit files");
576
577
        // Test cms access users without file access
578
        $this->objFromFixture(Member::class, 'security')->logIn();
579
        $this->assertFalse($file->canEdit(), "Security CMS users can't edit files");
580
581
        // Test asset-admin user
582
        $this->objFromFixture(Member::class, 'assetadmin')->logIn();
583
        $this->assertTrue($file->canEdit(), "Asset admin users can edit files");
584
585
        // Test admin
586
        $this->objFromFixture(Member::class, 'admin')->logIn();
587
        $this->assertTrue($file->canEdit(), "Admins can edit files");
588
    }
589
590
    public function testJoinPaths()
591
    {
592
        $this->assertEquals('name/file.jpg', File::join_paths('/name', 'file.jpg'));
593
        $this->assertEquals('name/file.jpg', File::join_paths('name', 'file.jpg'));
594
        $this->assertEquals('name/file.jpg', File::join_paths('/name', '/file.jpg'));
595
        $this->assertEquals('name/file.jpg', File::join_paths('name/', '/', 'file.jpg'));
596
        $this->assertEquals('file.jpg', File::join_paths('/', '/', 'file.jpg'));
597
        $this->assertEquals('', File::join_paths('/', '/'));
598
    }
599
600
    /**
601
     * Test that ini2bytes returns the number of bytes for a PHP ini style size declaration
602
     *
603
     * @param string $iniValue
604
     * @param int    $expected
605
     * @dataProvider ini2BytesProvider
606
     */
607
    public function testIni2Bytes($iniValue, $expected)
608
    {
609
        $this->assertSame($expected, File::ini2bytes($iniValue));
610
    }
611
612
    /**
613
     * @return array
614
     */
615
    public function ini2BytesProvider()
616
    {
617
        return [
618
            ['2k', 2 * 1024],
619
            ['512M', 512 * 1024 * 1024],
620
            ['1024g', 1024 * 1024 * 1024 * 1024],
621
            ['1024G', 1024 * 1024 * 1024 * 1024]
622
        ];
623
    }
624
625
    /**
626
     * @return AssetStore
627
     */
628
    protected function getAssetStore()
629
    {
630
        return Injector::inst()->get('AssetStore');
631
    }
632
}
633