Completed
Push — master ( 2fdc96...4f1f24 )
by Damian
12:09
created

UploadFieldTest::mockFileExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 2
1
<?php
2
3
/**
4
 * @package framework
5
 * @subpackage tests
6
 */
7
class UploadFieldTest extends FunctionalTest {
8
9
	protected static $fixture_file = 'UploadFieldTest.yml';
10
11
	protected $extraDataObjects = array('UploadFieldTest_Record');
12
13
	protected $requiredExtensions = array(
14
		'File' => array('UploadFieldTest_FileExtension')
15
	);
16
17
	protected $oldReadingMode = null;
18
19
	public function setUp() {
20
		parent::setUp();
21
22
		$this->loginWithPermission('ADMIN');
23
24
		// Save versioned state
25
		$this->oldReadingMode = Versioned::get_reading_mode();
26
		Versioned::reading_stage('Stage');
27
28
		// Set backend root to /UploadFieldTest
29
		AssetStoreTest_SpyStore::activate('UploadFieldTest');
30
31
		// Create a test folders for each of the fixture references
32
		foreach(Folder::get() as $folder) {
33
			$path = AssetStoreTest_SpyStore::getLocalPath($folder);
34
			Filesystem::makeFolder($path);
35
		}
36
37
		// Create a test files for each of the fixture references
38
		$files = File::get()->exclude('ClassName', 'Folder');
39
		foreach($files as $file) {
40
			$path = AssetStoreTest_SpyStore::getLocalPath($file);
41
			Filesystem::makeFolder(dirname($path));
42
			$fh = fopen($path, "w+");
43
			fwrite($fh, str_repeat('x', 1000000));
44
			fclose($fh);
45
		}
46
	}
47
48
	public function tearDown() {
49
		AssetStoreTest_SpyStore::reset();
50
		if($this->oldReadingMode) {
51
			Versioned::set_reading_mode($this->oldReadingMode);
52
		}
53
		parent::tearDown();
54
	}
55
56
	/**
57
	 * Test that files can be uploaded against an object with no relation
58
	 */
59
	public function testUploadNoRelation() {
60
		$tmpFileName = 'testUploadBasic.txt';
61
		$response = $this->mockFileUpload('NoRelationField', $tmpFileName);
62
		$this->assertFalse($response->isError());
63
		$uploadedFile = DataObject::get_one('File', array(
64
			'"File"."Name"' => $tmpFileName
65
		));
66
67
		$this->assertFileExists(AssetStoreTest_SpyStore::getLocalPath($uploadedFile));
68
		$this->assertTrue(is_object($uploadedFile), 'The file object is created');
69
	}
70
71
	/**
72
	 * Test that an object can be uploaded against an object with a has_one relation
73
	 */
74
	public function testUploadHasOneRelation() {
75
		// Unset existing has_one relation before re-uploading
76
		$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
77
		$record->HasOneFileID = null;
0 ignored issues
show
Documentation introduced by
The property HasOneFileID does not exist on object<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...
78
		$record->write();
79
80
		// Firstly, ensure the file can be uploaded
81
		$tmpFileName = 'testUploadHasOneRelation.txt';
82
		$response = $this->mockFileUpload('HasOneFile', $tmpFileName);
83
		$this->assertFalse($response->isError());
84
		$uploadedFile = DataObject::get_one('File', array(
85
			'"File"."Name"' => $tmpFileName
86
		));
87
		$this->assertTrue(is_object($uploadedFile), 'The file object is created');
88
		$this->assertFileExists(AssetStoreTest_SpyStore::getLocalPath($uploadedFile));
89
90
		// Secondly, ensure that simply uploading an object does not save the file against the relation
91
		$record = DataObject::get_by_id($record->class, $record->ID, false);
92
		$this->assertFalse($record->HasOneFile()->exists());
93
94
		// Thirdly, test submitting the form with the encoded data
95
		$response = $this->mockUploadFileIDs('HasOneFile', array($uploadedFile->ID));
96
		$this->assertEmpty($response['errors']);
97
		$record = DataObject::get_by_id($record->class, $record->ID, false);
98
		$this->assertTrue($record->HasOneFile()->exists());
99
		$this->assertEquals($record->HasOneFile()->Name, $tmpFileName);
100
	}
101
102
	/**
103
	 * Tests that has_one relations work with subclasses of File
104
	 */
105
	public function testUploadHasOneRelationWithExtendedFile() {
106
		// Unset existing has_one relation before re-uploading
107
		$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
108
		$record->HasOneExtendedFileID = null;
0 ignored issues
show
Documentation introduced by
The property HasOneExtendedFileID does not exist on object<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...
109
		$record->write();
110
111
		// Test that the file can be safely uploaded
112
		$tmpFileName = 'testUploadHasOneRelationWithExtendedFile.txt';
113
		$response = $this->mockFileUpload('HasOneExtendedFile', $tmpFileName);
114
		$this->assertFalse($response->isError());
115
		$uploadedFile = DataObject::get_one('UploadFieldTest_ExtendedFile', array(
116
			'"File"."Name"' => $tmpFileName
117
		));
118
		$this->assertTrue(is_object($uploadedFile), 'The file object is created');
119
		$this->assertFileExists(AssetStoreTest_SpyStore::getLocalPath($uploadedFile));
120
121
		// Test that the record isn't written to automatically
122
		$record = DataObject::get_by_id($record->class, $record->ID, false);
123
		$this->assertFalse($record->HasOneExtendedFile()->exists());
124
125
		// Test that saving the form writes the record
126
		$response = $this->mockUploadFileIDs('HasOneExtendedFile', array($uploadedFile->ID));
127
		$this->assertEmpty($response['errors']);
128
		$record = DataObject::get_by_id($record->class, $record->ID, false);
129
		$this->assertTrue($record->HasOneExtendedFile()->exists());
130
		$this->assertEquals($record->HasOneExtendedFile()->Name, $tmpFileName);
131
	}
132
133
134
	/**
135
	 * Test that has_many relations work with files
136
	 */
137
	public function testUploadHasManyRelation() {
138
		$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
139
140
		// Test that uploaded files can be posted to a has_many relation
141
		$tmpFileName = 'testUploadHasManyRelation.txt';
142
		$response = $this->mockFileUpload('HasManyFiles', $tmpFileName);
143
		$this->assertFalse($response->isError());
144
		$uploadedFile = DataObject::get_one('File', array(
145
			'"File"."Name"' => $tmpFileName
146
		));
147
		$this->assertTrue(is_object($uploadedFile), 'The file object is created');
148
		$this->assertFileExists(AssetStoreTest_SpyStore::getLocalPath($uploadedFile));
149
150
		// Test that the record isn't written to automatically
151
		$record = DataObject::get_by_id($record->class, $record->ID, false);
152
		$this->assertEquals(2, $record->HasManyFiles()->Count()); // Existing two files should be retained
153
154
		// Test that saving the form writes the record
155
		$ids = array_merge($record->HasManyFiles()->getIDList(), array($uploadedFile->ID));
156
		$response = $this->mockUploadFileIDs('HasManyFiles', $ids);
157
		$this->assertEmpty($response['errors']);
158
		$record = DataObject::get_by_id($record->class, $record->ID, false);
159
		$this->assertEquals(3, $record->HasManyFiles()->Count()); // New record should appear here now
160
	}
161
162
	/**
163
	 * Test that many_many relationships work with files
164
	 */
165
	public function testUploadManyManyRelation() {
166
		$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
167
		$relationCount = $record->ManyManyFiles()->Count();
168
169
		// Test that uploaded files can be posted to a many_many relation
170
		$tmpFileName = 'testUploadManyManyRelation.txt';
171
		$response = $this->mockFileUpload('ManyManyFiles', $tmpFileName);
172
		$this->assertFalse($response->isError());
173
		$uploadedFile = DataObject::get_one('File', array(
174
			'"File"."Name"' => $tmpFileName
175
		));
176
		$this->assertTrue(is_object($uploadedFile), 'The file object is created');
177
		$this->assertFileExists(AssetStoreTest_SpyStore::getLocalPath($uploadedFile));
178
179
		// Test that the record isn't written to automatically
180
		$record = DataObject::get_by_id($record->class, $record->ID, false);
181
		// Existing file count should be retained
182
		$this->assertEquals($relationCount, $record->ManyManyFiles()->Count());
183
184
		// Test that saving the form writes the record
185
		$ids = array_merge($record->ManyManyFiles()->getIDList(), array($uploadedFile->ID));
186
		$response = $this->mockUploadFileIDs('ManyManyFiles', $ids);
187
		$this->assertEmpty($response['errors']);
188
		$record = DataObject::get_by_id($record->class, $record->ID, false);
189
		$record->flushCache();
190
		// New record should appear here now
191
		$this->assertEquals($relationCount + 1, $record->ManyManyFiles()->Count());
192
	}
193
194
	/**
195
	 * Partially covered by {@link UploadTest->testUploadAcceptsAllowedExtension()},
196
	 * but this test additionally verifies that those constraints are actually enforced
197
	 * in this controller method.
198
	 */
199
	public function testAllowedExtensions() {
0 ignored issues
show
Coding Style introduced by
testAllowedExtensions uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
200
		// Test invalid file
201
		// Relies on Upload_Validator failing to allow this extension
202
		$invalidFile = 'invalid.php';
203
		$_FILES = array('AllowedExtensionsField' => $this->getUploadFile($invalidFile));
204
		$response = $this->post(
205
			'UploadFieldTest_Controller/Form/field/AllowedExtensionsField/upload',
206
			array('AllowedExtensionsField' => $this->getUploadFile($invalidFile))
207
		);
208
		$response = json_decode($response->getBody(), true);
209
		$this->assertTrue(array_key_exists('error', $response[0]));
210
		$this->assertContains('Extension is not allowed', $response[0]['error']);
211
212
		// Test valid file
213
		$validFile = 'valid.txt';
214
		$_FILES = array('AllowedExtensionsField' => $this->getUploadFile($validFile));
215
		$response = $this->post(
216
			'UploadFieldTest_Controller/Form/field/AllowedExtensionsField/upload',
217
			array('AllowedExtensionsField' => $this->getUploadFile($validFile))
218
		);
219
		$response = json_decode($response->getBody(), true);
220
		$this->assertFalse(array_key_exists('error', $response[0]));
221
222
		// Test that setAllowedExtensions rejects extensions explicitly denied by File.allowed_extensions
223
		// Relies on File::validate failing to allow this extension
224
		$invalidFile = 'invalid.php';
225
		$_FILES = array('AllowedExtensionsField' => $this->getUploadFile($invalidFile));
226
		$response = $this->post(
227
			'UploadFieldTest_Controller/Form/field/InvalidAllowedExtensionsField/upload',
228
			array('InvalidAllowedExtensionsField' => $this->getUploadFile($invalidFile))
229
		);
230
		$response = json_decode($response->getBody(), true);
231
		$this->assertTrue(array_key_exists('error', $response[0]));
232
		$this->assertContains('Extension is not allowed', $response[0]['error']);
233
234
	}
235
236
	/**
237
	 * Test that has_one relations do not support multiple files
238
	 */
239
	public function testAllowedMaxFileNumberWithHasOne() {
240
		// Get references for each file to upload
241
		$file1 = $this->objFromFixture('File', 'file1');
242
		$file2 = $this->objFromFixture('File', 'file2');
243
		$fileIDs = array($file1->ID, $file2->ID);
244
245
		// Test each of the three cases - has one with no max filel limit, has one with a limit of
246
		// one, has one with a limit of more than one (makes no sense, but should test it anyway).
247
		// Each of them should public function in the same way - attaching the first file should work, the
248
		// second should cause an error.
249
		foreach (array('HasOneFile', 'HasOneFileMaxOne', 'HasOneFileMaxTwo') as $recordName) {
250
251
			// Unset existing has_one relation before re-uploading
252
			$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
253
			$record->{"{$recordName}ID"} = null;
254
			$record->write();
255
256
			// Post form with two files for this field, should result in an error
257
			$response = $this->mockUploadFileIDs($recordName, $fileIDs);
258
			$isError = !empty($response['errors']);
259
260
			// Strictly, a has_one should not allow two files, but this is overridden
261
			// by the setAllowedMaxFileNumber(2) call
262
			$maxFiles = ($recordName === 'HasOneFileMaxTwo') ? 2 : 1;
263
264
			// Assert that the form fails if the maximum number of files is exceeded
265
			$this->assertTrue((count($fileIDs) > $maxFiles) == $isError);
266
		}
267
	}
268
269
	/**
270
	 * Test that max number of items on has_many is validated
271
	 */
272
	public function testAllowedMaxFileNumberWithHasMany() {
273
		// The 'HasManyFilesMaxTwo' field has a maximum of two files able to be attached to it.
274
		// We want to add files to it until we attempt to add the third. We expect that the first
275
		// two should work and the third will fail.
276
		$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
277
		$record->HasManyFilesMaxTwo()->removeAll();
278
		$this->assertCount(0, $record->HasManyFilesMaxTwo());
279
280
		// Get references for each file to upload
281
		$file1 = $this->objFromFixture('File', 'file1');
282
		$file2 = $this->objFromFixture('File', 'file2');
283
		$file3 = $this->objFromFixture('File', 'file3');
284
		$this->assertTrue($file1->exists());
285
		$this->assertTrue($file2->exists());
286
		$this->assertTrue($file3->exists());
287
288
		// Write the first element, should be okay.
289
		$response = $this->mockUploadFileIDs('HasManyFilesMaxTwo', array($file1->ID));
290
		$this->assertEmpty($response['errors']);
291
		$this->assertCount(1, $record->HasManyFilesMaxTwo());
292
		$this->assertContains($file1->ID, $record->HasManyFilesMaxTwo()->getIDList());
293
294
295
		$record->HasManyFilesMaxTwo()->removeAll();
296
		$this->assertCount(0, $record->HasManyFilesMaxTwo());
297
		$this->assertTrue($file1->exists());
298
		$this->assertTrue($file2->exists());
299
		$this->assertTrue($file3->exists());
300
301
302
303
		// Write the second element, should be okay.
304
		$response = $this->mockUploadFileIDs('HasManyFilesMaxTwo', array($file1->ID, $file2->ID));
305
		$this->assertEmpty($response['errors']);
306
		$this->assertCount(2, $record->HasManyFilesMaxTwo());
307
		$this->assertContains($file1->ID, $record->HasManyFilesMaxTwo()->getIDList());
308
		$this->assertContains($file2->ID, $record->HasManyFilesMaxTwo()->getIDList());
309
310
		$record->HasManyFilesMaxTwo()->removeAll();
311
		$this->assertCount(0, $record->HasManyFilesMaxTwo());
312
		$this->assertTrue($file1->exists());
313
		$this->assertTrue($file2->exists());
314
		$this->assertTrue($file3->exists());
315
316
317
		// Write the third element, should result in error.
318
		$response = $this->mockUploadFileIDs('HasManyFilesMaxTwo', array($file1->ID, $file2->ID, $file3->ID));
319
		$this->assertNotEmpty($response['errors']);
320
		$this->assertCount(0, $record->HasManyFilesMaxTwo());
321
	}
322
323
	/**
324
	 * Test that files can be removed from has_one relations
325
	 */
326
	public function testRemoveFromHasOne() {
327
		$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
328
		$file1 = $this->objFromFixture('File', 'file1');
329
330
		// Check record exists
331
		$this->assertTrue($record->HasOneFile()->exists());
332
333
		// Remove from record
334
		$response = $this->mockUploadFileIDs('HasOneFile', array());
335
		$this->assertEmpty($response['errors']);
336
337
		// Check file is removed
338
		$record = DataObject::get_by_id($record->class, $record->ID, false);
339
		$this->assertFalse($record->HasOneFile()->exists());
340
341
		// Check file object itself exists
342
		$this->assertFileExists(
343
			AssetStoreTest_SpyStore::getLocalPath($file1),
344
			'File is only detached, not deleted from filesystem'
345
		);
346
	}
347
348
	/**
349
	 * Test that items can be removed from has_many
350
	 */
351
	public function testRemoveFromHasMany() {
352
		$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
353
		$file2 = $this->objFromFixture('File', 'file2');
354
		$file3 = $this->objFromFixture('File', 'file3');
355
356
		// Check record has two files attached
357
		$this->assertEquals(array('File2', 'File3'), $record->HasManyFiles()->column('Title'));
358
359
		// Remove file 2
360
		$response = $this->mockUploadFileIDs('HasManyFiles', array($file3->ID));
361
		$this->assertEmpty($response['errors']);
362
363
		// check only file 3 is left
364
		$record = DataObject::get_by_id($record->class, $record->ID, false);
365
		$this->assertEquals(array('File3'), $record->HasManyFiles()->column('Title'));
366
367
		// Check file 2 object itself exists
368
		$this->assertFileExists(
369
			AssetStoreTest_SpyStore::getLocalPath($file3),
370
			'File is only detached, not deleted from filesystem'
371
		);
372
	}
373
374
	/**
375
	 * Test that items can be removed from many_many
376
	 */
377
	public function testRemoveFromManyMany() {
378
		$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
379
		$file4 = $this->objFromFixture('File', 'file4');
380
		$file5 = $this->objFromFixture('File', 'file5');
381
382
		// Check that both files are currently set
383
		$this->assertContains('File4', $record->ManyManyFiles()->column('Title'));
384
		$this->assertContains('File5', $record->ManyManyFiles()->column('Title'));
385
386
		// Remove file 4
387
		$response = $this->mockUploadFileIDs('ManyManyFiles', array($file5->ID));
388
		$this->assertEmpty($response['errors']);
389
390
		// check only file 5 is left
391
		$record = DataObject::get_by_id($record->class, $record->ID, false);
392
		$this->assertNotContains('File4', $record->ManyManyFiles()->column('Title'));
393
		$this->assertContains('File5', $record->ManyManyFiles()->column('Title'));
394
395
		// check file 4 object exists
396
		$this->assertFileExists(
397
			AssetStoreTest_SpyStore::getLocalPath($file4),
398
			'File is only detached, not deleted from filesystem'
399
		);
400
	}
401
402
	/**
403
	 * Test that files can be deleted from has_one
404
	 */
405
	public function testDeleteFromHasOne() {
406
		$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
407
		$file1 = $this->objFromFixture('File', 'file1');
408
409
		// Check that file initially exists
410
		$this->assertTrue($record->HasOneFile()->exists());
411
		$this->assertFileExists(AssetStoreTest_SpyStore::getLocalPath($file1));
412
413
		// Delete file and update record
414
		$response = $this->mockFileDelete('HasOneFile', $file1->ID);
415
		$this->assertFalse($response->isError());
416
		$response = $this->mockUploadFileIDs('HasOneFile', array());
417
		$this->assertEmpty($response['errors']);
418
419
		// Check that file is not set against record
420
		$record = DataObject::get_by_id($record->class, $record->ID, false);
421
		$this->assertFalse($record->HasOneFile()->exists());
422
	}
423
424
	/**
425
	 * Test that files can be deleted from has_many
426
	 */
427
	public function testDeleteFromHasMany() {
428
		$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
429
		$file2 = $this->objFromFixture('File', 'file2');
430
		$file3 = $this->objFromFixture('File', 'file3');
431
432
		// Check that files initially exists
433
		$this->assertEquals(array('File2', 'File3'), $record->HasManyFiles()->column('Title'));
434
		$this->assertFileExists(AssetStoreTest_SpyStore::getLocalPath($file2));
435
		$this->assertFileExists(AssetStoreTest_SpyStore::getLocalPath($file3));
436
437
		// Delete dataobject file and update record without file 2
438
		$response = $this->mockFileDelete('HasManyFiles', $file2->ID);
439
		$this->assertFalse($response->isError());
440
		$response = $this->mockUploadFileIDs('HasManyFiles', array($file3->ID));
441
		$this->assertEmpty($response['errors']);
442
443
		// Test that file is removed from record
444
		$record = DataObject::get_by_id($record->class, $record->ID, false);
445
		$this->assertEquals(array('File3'), $record->HasManyFiles()->column('Title'));
446
	}
447
448
	/**
449
	 * Test that files can be deleted from many_many and the filesystem
450
	 */
451
	public function testDeleteFromManyMany() {
452
		$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
453
		$file4 = $this->objFromFixture('File', 'file4');
454
		$file5 = $this->objFromFixture('File', 'file5');
455
		$fileNoDelete = $this->objFromFixture('File', 'file-nodelete');
456
457
		// Test that files initially exist
458
		$setFiles = $record->ManyManyFiles()->column('Title');
459
		$this->assertContains('File4', $setFiles);
460
		$this->assertContains('File5', $setFiles);
461
		$this->assertContains('nodelete.txt', $setFiles);
462
		$this->assertFileExists(AssetStoreTest_SpyStore::getLocalPath($file4));
463
		$this->assertFileExists(AssetStoreTest_SpyStore::getLocalPath($file5));
464
		$this->assertFileExists(AssetStoreTest_SpyStore::getLocalPath($fileNoDelete));
465
466
		// Delete physical file and update record without file 4
467
		$response = $this->mockFileDelete('ManyManyFiles', $file4->ID);
468
		$this->assertFalse($response->isError());
469
470
		// Check file is removed from record
471
		$record = DataObject::get_by_id($record->class, $record->ID, false);
472
		$this->assertNotContains('File4', $record->ManyManyFiles()->column('Title'));
473
		$this->assertContains('File5', $record->ManyManyFiles()->column('Title'));
474
475
		// Test record-based permissions
476
		$response = $this->mockFileDelete('ManyManyFiles', $fileNoDelete->ID);
477
		$this->assertEquals(403, $response->getStatusCode());
478
479
		// Test that folders can't be deleted
480
		$folder = $this->objFromFixture('Folder', 'folder1-subfolder1');
481
		$response = $this->mockFileDelete('ManyManyFiles', $folder->ID);
482
		$this->assertEquals(403, $response->getStatusCode());
483
	}
484
485
	/**
486
	 * Test control output html
487
	 */
488
	public function testView() {
489
		$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
490
		$file4 = $this->objFromFixture('File', 'file4');
491
		$file5 = $this->objFromFixture('File', 'file5');
492
		$fileNoView = $this->objFromFixture('File', 'file-noview');
493
		$fileNoEdit = $this->objFromFixture('File', 'file-noedit');
494
		$fileNoDelete = $this->objFromFixture('File', 'file-nodelete');
495
496
		$response = $this->get('UploadFieldTest_Controller');
497
		$this->assertFalse($response->isError());
498
499
		$parser = new CSSContentParser($response->getBody());
500
		$items = $parser->getBySelector(
501
			'#UploadFieldTestForm_Form_HasManyNoViewFiles_Holder .ss-uploadfield-files .ss-uploadfield-item'
502
		);
503
		$ids = array();
504
		foreach($items as $item) $ids[] = (int)$item['data-fileid'];
505
506
		$this->assertContains($file4->ID, $ids, 'Views related file');
507
		$this->assertContains($file5->ID, $ids, 'Views related file');
508
		$this->assertNotContains($fileNoView->ID, $ids, "Doesn't view files without view permissions");
509
		$this->assertContains($fileNoEdit->ID, $ids, "Views files without edit permissions");
510
		$this->assertContains($fileNoDelete->ID, $ids, "Views files without delete permissions");
511
	}
512
513
	public function testEdit() {
514
		$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
515
		$file4 = $this->objFromFixture('File', 'file4');
516
		$fileNoEdit = $this->objFromFixture('File', 'file-noedit');
517
		$folder = $this->objFromFixture('Folder', 'folder1-subfolder1');
518
519
		$response = $this->mockFileEditForm('ManyManyFiles', $file4->ID);
520
		$this->assertFalse($response->isError());
521
522
		$response = $this->mockFileEdit('ManyManyFiles', $file4->ID, array('Title' => 'File 4 modified'));
523
		$this->assertFalse($response->isError());
524
525
		$record = DataObject::get_by_id($record->class, $record->ID, false);
526
		$file4 = DataObject::get_by_id($file4->class, $file4->ID, false);
527
		$this->assertEquals('File 4 modified', $file4->Title);
528
529
		// Test record-based permissions
530
		$response = $this->mockFileEditForm('ManyManyFiles', $fileNoEdit->ID);
531
		$this->assertEquals(403, $response->getStatusCode());
532
533
		$response = $this->mockFileEdit('ManyManyFiles', $fileNoEdit->ID, array());
534
		$this->assertEquals(403, $response->getStatusCode());
535
536
		// Test folder permissions
537
		$response = $this->mockFileEditForm('ManyManyFiles', $folder->ID);
538
		$this->assertEquals(403, $response->getStatusCode());
539
540
		$response = $this->mockFileEdit('ManyManyFiles', $folder->ID, array());
541
		$this->assertEquals(403, $response->getStatusCode());
542
	}
543
544
	public function testGetRecord() {
545
		$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
546
		$form = $this->getMockForm();
547
548
		$field = UploadField::create('MyField');
549
		$field->setForm($form);
550
		$this->assertNull($field->getRecord(), 'Returns no record by default');
551
552
		$field = UploadField::create('MyField');
553
		$field->setForm($form);
554
		$form->loadDataFrom($record);
0 ignored issues
show
Bug introduced by
It seems like $record defined by $this->objFromFixture('U...est_Record', 'record1') on line 545 can be null; however, Form::loadDataFrom() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
555
		$this->assertEquals($record, $field->getRecord(), 'Returns record from form if available');
556
557
		$field = UploadField::create('MyField');
558
		$field->setForm($form);
559
		$field->setRecord($record);
0 ignored issues
show
Bug introduced by
It seems like $record defined by $this->objFromFixture('U...est_Record', 'record1') on line 545 can be null; however, UploadField::setRecord() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
560
		$this->assertEquals($record, $field->getRecord(), 'Returns record when set explicitly');
561
	}
562
563
	public function testSetItems() {
564
		$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
565
		$items = new ArrayList(array(
566
			$this->objFromFixture('File', 'file1'),
567
			$this->objFromFixture('File', 'file2')
568
		));
569
570
		// Field with no record attached
571
		$field = UploadField::create('DummyField');
572
		$field->setItems($items);
573
		$this->assertEquals(array('File1', 'File2'), $field->getItems()->column('Title'));
574
575
		// Anonymous field
576
		$field = UploadField::create('MyField');
577
		$field->setRecord($record);
0 ignored issues
show
Bug introduced by
It seems like $record defined by $this->objFromFixture('U...est_Record', 'record1') on line 564 can be null; however, UploadField::setRecord() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
578
		$field->setItems($items);
579
		$this->assertEquals(array('File1', 'File2'), $field->getItems()->column('Title'));
580
581
		// Field with has_one auto-detected
582
		$field = UploadField::create('HasOneFile');
583
		$field->setRecord($record);
0 ignored issues
show
Bug introduced by
It seems like $record defined by $this->objFromFixture('U...est_Record', 'record1') on line 564 can be null; however, UploadField::setRecord() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
584
		$field->setItems($items);
585
		$this->assertEquals(array('File1', 'File2'), $field->getItems()->column('Title'),
586
			'Allows overwriting of items even when relationship is detected'
587
		);
588
	}
589
590
	public function testGetItems() {
591
		$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
592
593
		// Anonymous field
594
		$field = UploadField::create('MyField');
595
		$field->setValue(null, $record);
596
		$this->assertEquals(array(), $field->getItems()->column('Title'));
597
598
		// Field with has_one auto-detected
599
		$field = UploadField::create('HasOneFile');
600
		$field->setValue(null, $record);
601
		$this->assertEquals(array('File1'), $field->getItems()->column('Title'));
602
603
		// Field with has_many auto-detected
604
		$field = UploadField::create('HasManyFiles');
605
		$field->setValue(null, $record);
606
		$this->assertEquals(array('File2', 'File3'), $field->getItems()->column('Title'));
607
608
		// Field with many_many auto-detected
609
		$field = UploadField::create('ManyManyFiles');
610
		$field->setValue(null, $record);
611
		$this->assertNotContains('File1',$field->getItems()->column('Title'));
612
		$this->assertNotContains('File2',$field->getItems()->column('Title'));
613
		$this->assertNotContains('File3',$field->getItems()->column('Title'));
614
		$this->assertContains('File4',$field->getItems()->column('Title'));
615
		$this->assertContains('File5',$field->getItems()->column('Title'));
616
	}
617
618
	public function testReadonly() {
619
		$response = $this->get('UploadFieldTest_Controller');
620
		$this->assertFalse($response->isError());
621
622
		$parser = new CSSContentParser($response->getBody());
623
624
		$this->assertFalse(
625
			(bool)$parser->getBySelector(
626
				'#UploadFieldTestForm_Form_ReadonlyField .ss-uploadfield-files .ss-uploadfield-item .ss-ui-button'
627
				),
628
			'Removes all buttons on items');
629
		$this->assertFalse(
630
			(bool)$parser->getBySelector('#UploadFieldTestForm_Form_ReadonlyField .ss-uploadfield-dropzone'),
631
			'Removes dropzone'
632
		);
633
		$this->assertFalse(
634
			(bool)$parser->getBySelector(
635
				'#UploadFieldTestForm_Form_ReadonlyField .ss-uploadfield-addfile'
636
			),
637
			'Entire "add" area'
638
		);
639
	}
640
641
	public function testDisabled() {
642
		$response = $this->get('UploadFieldTest_Controller');
643
		$this->assertFalse($response->isError());
644
645
		$parser = new CSSContentParser($response->getBody());
646
		$this->assertFalse(
647
			(bool)$parser->getBySelector(
648
				'#UploadFieldTestForm_Form_DisabledField .ss-uploadfield-files .ss-uploadfield-item .ss-ui-button'
649
			),
650
			'Removes all buttons on items');
651
		$this->assertFalse((bool)$parser->getBySelector(
652
			'#UploadFieldTestForm_Form_DisabledField .ss-uploadfield-dropzone'
653
			),
654
			'Removes dropzone');
655
		$this->assertFalse(
656
			(bool)$parser->getBySelector('#UploadFieldTestForm_Form_DisabledField .ss-uploadfield-addfile'),
657
			'Entire "add" area'
658
		);
659
	}
660
661
	public function testCanUpload() {
662
		$response = $this->get('UploadFieldTest_Controller');
663
		$this->assertFalse($response->isError());
664
665
		$parser = new CSSContentParser($response->getBody());
666
		$this->assertFalse(
667
			(bool)$parser->getBySelector(
668
				'#UploadFieldTestForm_Form_CanUploadFalseField_Holder .ss-uploadfield-dropzone'
669
			),
670
			'Removes dropzone');
671
		$this->assertTrue(
672
			(bool)$parser->getBySelector(
673
				'#UploadFieldTestForm_Form_CanUploadFalseField_Holder .ss-uploadfield-fromfiles'
674
			),
675
			'Keeps "From files" button'
676
		);
677
	}
678
679
	public function testCanUploadWithPermissionCode() {
680
		$field = UploadField::create('MyField');
681
		Session::clear("loggedInAs");
682
683
		$field->setCanUpload(true);
684
		$this->assertTrue($field->canUpload());
685
686
		$field->setCanUpload(false);
687
		$this->assertFalse($field->canUpload());
688
689
		$this->loginWithPermission('ADMIN');
690
691
		$field->setCanUpload(false);
692
		$this->assertFalse($field->canUpload());
693
694
		$field->setCanUpload('ADMIN');
695
		$this->assertTrue($field->canUpload());
696
	}
697
698
	public function testCanAttachExisting() {
699
		$response = $this->get('UploadFieldTest_Controller');
700
		$this->assertFalse($response->isError());
701
702
		$parser = new CSSContentParser($response->getBody());
703
		$this->assertTrue(
704
			(bool)$parser->getBySelector(
705
				'#UploadFieldTestForm_Form_CanAttachExistingFalseField_Holder .ss-uploadfield-fromcomputer-fileinput'
706
			),
707
			'Keeps input file control'
708
		);
709
		$this->assertFalse(
710
			(bool)$parser->getBySelector(
711
				'#UploadFieldTestForm_Form_CanAttachExistingFalseField_Holder .ss-uploadfield-fromfiles'
712
			),
713
			'Removes "From files" button'
714
		);
715
716
		// Test requests to select files have the correct given permission
717
		$response2 = $this->get('UploadFieldTest_Controller/Form/field/CanAttachExistingFalseField/select');
718
		$this->assertEquals(403, $response2->getStatusCode());
719
		$response3 = $this->get('UploadFieldTest_Controller/Form/field/HasOneFile/select');
720
		$this->assertEquals(200, $response3->getStatusCode());
721
	}
722
723
	public function testSelect() {
724
		$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
725
		$file4 = $this->objFromFixture('File', 'file4');
726
		$fileSubfolder = $this->objFromFixture('File', 'file-subfolder');
727
728
		$response = $this->get('UploadFieldTest_Controller/Form/field/ManyManyFiles/select/');
729
		$this->assertFalse($response->isError());
730
731
		// A bit too much coupling with GridField, but a full template overload would make things too complex
732
		$parser = new CSSContentParser($response->getBody());
733
		$items = $parser->getBySelector('.ss-gridfield-item');
734
		$itemIDs = array_map(create_function('$el', 'return (int)$el["data-id"];'), $items);
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
735
		$this->assertContains($file4->ID, $itemIDs, 'Contains file in assigned folder');
736
		$this->assertContains($fileSubfolder->ID, $itemIDs, 'Contains file in subfolder');
737
	}
738
739
	public function testSelectWithDisplayFolderName() {
740
		$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
741
		$file4 = $this->objFromFixture('File', 'file4');
742
		$fileSubfolder = $this->objFromFixture('File', 'file-subfolder');
743
744
		$response = $this->get('UploadFieldTest_Controller/Form/field/HasManyDisplayFolder/select/');
745
		$this->assertFalse($response->isError());
746
747
		// A bit too much coupling with GridField, but a full template overload would make things too complex
748
		$parser = new CSSContentParser($response->getBody());
749
		$items = $parser->getBySelector('.ss-gridfield-item');
750
		$itemIDs = array_map(create_function('$el', 'return (int)$el["data-id"];'), $items);
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
751
		$this->assertContains($file4->ID, $itemIDs, 'Contains file in assigned folder');
752
		$this->assertNotContains($fileSubfolder->ID, $itemIDs, 'Does not contain file in subfolder');
753
	}
754
755
	/**
756
	 * Test that UploadField:overwriteWarning cannot overwrite Upload:replaceFile
757
	 */
758
	public function testConfigOverwriteWarningCannotRelaceFiles() {
759
		Upload::config()->replaceFile = false;
0 ignored issues
show
Documentation introduced by
The property replaceFile does not exist on object<Config_ForClass>. 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...
760
		UploadField::config()->defaultConfig = array_merge(
0 ignored issues
show
Documentation introduced by
The property defaultConfig does not exist on object<Config_ForClass>. 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...
761
			UploadField::config()->defaultConfig, array('overwriteWarning' => true)
762
		);
763
764
		$tmpFileName = 'testUploadBasic.txt';
765
		$response = $this->mockFileUpload('NoRelationField', $tmpFileName);
766
		$this->assertFalse($response->isError());
767
		$responseData = Convert::json2array($response->getBody());
768
		$uploadedFile = DataObject::get_by_id('File', (int) $responseData[0]['id']);
769
		$this->assertTrue(is_object($uploadedFile), 'The file object is created');
770
		$this->assertFileExists(AssetStoreTest_SpyStore::getLocalPath($uploadedFile));
771
772
		$tmpFileName = 'testUploadBasic.txt';
773
		$response = $this->mockFileUpload('NoRelationField', $tmpFileName);
774
		$this->assertFalse($response->isError());
775
		$responseData = Convert::json2array($response->getBody());
776
		$uploadedFile2 = DataObject::get_by_id('File', (int) $responseData[0]['id']);
777
		$this->assertTrue(is_object($uploadedFile2), 'The file object is created');
778
		$this->assertFileExists(AssetStoreTest_SpyStore::getLocalPath($uploadedFile2));
779
		$this->assertTrue(
780
			$uploadedFile->Filename !== $uploadedFile2->Filename,
781
			'Filename is not the same'
782
		);
783
		$this->assertTrue(
784
			$uploadedFile->ID !== $uploadedFile2->ID,
785
			'File database record is not the same'
786
		);
787
	}
788
789
	/**
790
	 * Tests that UploadField::fileexist works
791
	 */
792
	public function testFileExists() {
793
		// Check that fileexist works on subfolders
794
		$nonFile = uniqid().'.txt';
795
		$responseEmpty = $this->mockFileExists('NoRelationField', $nonFile);
796
		$responseEmptyData = json_decode($responseEmpty->getBody());
797
		$this->assertFalse($responseEmpty->isError());
798
		$this->assertFalse($responseEmptyData->exists);
799
800
		// Check that filexists works on root folder
801
		$responseRoot = $this->mockFileExists('RootFolderTest', $nonFile);
802
		$responseRootData = json_decode($responseRoot->getBody());
803
		$this->assertFalse($responseRoot->isError());
804
		$this->assertFalse($responseRootData->exists);
805
806
		// Check that uploaded files can be detected in the root
807
		$tmpFileName = 'testUploadBasic.txt';
808
		$response = $this->mockFileUpload('RootFolderTest', $tmpFileName);
809
		$this->assertFalse($response->isError());
810
		$this->assertFileExists(ASSETS_PATH . "/UploadFieldTest/.protected/315ae4c3d4/$tmpFileName");
811
		$responseExists = $this->mockFileExists('RootFolderTest', $tmpFileName);
812
		$responseExistsData = json_decode($responseExists->getBody());
813
		$this->assertFalse($responseExists->isError());
814
		$this->assertTrue($responseExistsData->exists);
815
816
		// Check that uploaded files can be detected
817
		$response = $this->mockFileUpload('NoRelationField', $tmpFileName);
818
		$this->assertFalse($response->isError());
819
		$this->assertFileExists(ASSETS_PATH . "/UploadFieldTest/.protected/UploadedFiles/315ae4c3d4/$tmpFileName");
820
		$responseExists = $this->mockFileExists('NoRelationField', $tmpFileName);
821
		$responseExistsData = json_decode($responseExists->getBody());
822
		$this->assertFalse($responseExists->isError());
823
		$this->assertTrue($responseExistsData->exists);
824
825
		// Test that files with invalid characters are rewritten safely and both report exists
826
		// Check that uploaded files can be detected in the root
827
		$tmpFileName = '_test___Upload___Bad.txt';
828
		$tmpFileNameExpected = 'test-Upload-Bad.txt';
829
		$response = $this->mockFileUpload('NoRelationField', $tmpFileName);
830
		$this->assertFalse($response->isError());
831
		$this->assertFileExists(ASSETS_PATH . "/UploadFieldTest/.protected/UploadedFiles/315ae4c3d4/$tmpFileNameExpected");
832
		// With original file
833
		$responseExists = $this->mockFileExists('NoRelationField', $tmpFileName);
834
		$responseExistsData = json_decode($responseExists->getBody());
835
		$this->assertFalse($responseExists->isError());
836
		$this->assertTrue($responseExistsData->exists);
837
		// With rewritten file
838
		$responseExists = $this->mockFileExists('NoRelationField', $tmpFileNameExpected);
839
		$responseExistsData = json_decode($responseExists->getBody());
840
		$this->assertFalse($responseExists->isError());
841
		$this->assertTrue($responseExistsData->exists);
842
843
		// Test that attempts to navigate outside of the directory return false
844
		$responseExists = $this->mockFileExists('NoRelationField', "../../../../var/private/$tmpFileName");
845
		$this->assertTrue($responseExists->isError());
846
		$this->assertContains('File is not a valid upload', $responseExists->getBody());
847
	}
848
849
	protected function getMockForm() {
850
		return new Form(new Controller(), 'Form', new FieldList(), new FieldList());
851
	}
852
853
	/**
854
	 * @return Array Emulating an entry in the $_FILES superglobal
855
	 */
856
	protected function getUploadFile($tmpFileName = 'UploadFieldTest-testUpload.txt') {
857
		$tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName;
858
		$tmpFileContent = '';
859
		for($i=0; $i<10000; $i++) $tmpFileContent .= '0';
860
		file_put_contents($tmpFilePath, $tmpFileContent);
861
862
		// emulates the $_FILES array
863
		return array(
864
			'name' => array('Uploads' => array($tmpFileName)),
865
			'type' => array('Uploads' => array('text/plaintext')),
866
			'size' => array('Uploads' => array(filesize($tmpFilePath))),
867
			'tmp_name' => array('Uploads' => array($tmpFilePath)),
868
			'error' => array('Uploads' => array(UPLOAD_ERR_OK)),
869
		);
870
	}
871
872
	/**
873
	 * Simulates a form post to the test controller with the specified file IDs
874
	 *
875
	 * @param string $fileField Name of field to assign ids to
876
	 * @param array $ids list of file IDs
877
	 * @return boolean Array with key 'errors'
878
	 */
879
	protected function mockUploadFileIDs($fileField, $ids) {
880
881
		// collate file ids
882
		$files = array();
883
		foreach($ids as $id) {
884
			$files[$id] = $id;
885
		}
886
887
		$data = array(
888
			'action_submit' => 1
889
		);
890
		if($files) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $files of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
891
			// Normal post requests can't submit empty array values for fields
892
			$data[$fileField] = array('Files' => $files);
893
		}
894
895
		$form = new UploadFieldTestForm();
896
		$form->loadDataFrom($data, true);
897
898
		if($form->validate()) {
899
			$record = $form->getRecord();
900
			$form->saveInto($record);
901
			$record->write();
902
			return array('errors' => null);
903
		} else {
904
			return array('errors' => $form->getValidator()->getErrors());
905
		}
906
	}
907
908
	/**
909
	 * Simulates a file upload
910
	 *
911
	 * @param string $fileField Name of the field to mock upload for
912
	 * @param array $tmpFileName Name of temporary file to upload
913
	 * @return SS_HTTPResponse form response
914
	 */
915
	protected function mockFileUpload($fileField, $tmpFileName) {
0 ignored issues
show
Coding Style introduced by
mockFileUpload uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
916
		$upload = $this->getUploadFile($tmpFileName);
917
		$_FILES = array($fileField => $upload);
918
		return $this->post(
919
			"UploadFieldTest_Controller/Form/field/{$fileField}/upload",
920
			array($fileField => $upload)
921
		);
922
	}
923
924
	protected function mockFileExists($fileField, $fileName) {
925
		return $this->get(
926
			"UploadFieldTest_Controller/Form/field/{$fileField}/fileexists?filename=".urlencode($fileName)
927
		);
928
	}
929
930
	/**
931
	 * Gets the edit form for the given file
932
	 *
933
	 * @param string $fileField Name of the field
934
	 * @param integer $fileID ID of the file to delete
935
	 * @return SS_HTTPResponse form response
936
	 */
937
	protected function mockFileEditForm($fileField, $fileID) {
938
		return $this->get(
939
			"UploadFieldTest_Controller/Form/field/{$fileField}/item/{$fileID}/edit"
940
		);
941
	}
942
943
	/**
944
	 * Mocks edit submissions to a file
945
	 *
946
	 * @param string $fileField Name of the field
947
	 * @param integer $fileID ID of the file to delete
948
	 * @param array $fields Fields to update
949
	 * @return SS_HTTPResponse form response
950
	 */
951
	protected function mockFileEdit($fileField, $fileID, $fields = array()) {
952
		return $this->post(
953
			"UploadFieldTest_Controller/Form/field/{$fileField}/item/{$fileID}/EditForm",
954
			$fields
955
		);
956
	}
957
958
	/**
959
	 * Simulates a physical file deletion
960
	 *
961
	 * @param string $fileField Name of the field
962
	 * @param integer $fileID ID of the file to delete
963
	 * @return SS_HTTPResponse form response
964
	 */
965
	protected function mockFileDelete($fileField, $fileID) {
966
		return $this->post(
967
			"UploadFieldTest_Controller/Form/field/{$fileField}/item/{$fileID}/delete",
968
			array()
969
		);
970
	}
971
972
	public function get($url, $session = null, $headers = null, $cookies = null) {
973
		// Inject stage=Stage into the URL, to force working on draft
974
		$url = $this->addStageToUrl($url);
975
		return parent::get($url, $session, $headers, $cookies);
976
	}
977
978
	public function post($url, $data, $headers = null, $session = null, $body = null, $cookies = null) {
979
		// Inject stage=Stage into the URL, to force working on draft
980
		$url = $this->addStageToUrl($url);
981
		return parent::post($url, $data, $headers, $session, $body, $cookies);
982
	}
983
984
	/**
985
	 * Adds ?stage=Stage to url
986
	 *
987
	 * @param string $url
988
	 * @return string
989
	 */
990
	protected function addStageToUrl($url) {
991
		if(stripos($url, 'stage=Stage') === false) {
992
			if(stripos($url, '?') === false) {
993
				$url .= '?stage=Stage';
994
			} else {
995
				$url .= '&stage=Stage';
996
			}
997
		}
998
		return $url;
999
	}
1000
1001
}
1002
1003
class UploadFieldTest_Record extends DataObject implements TestOnly {
1004
1005
	private static $db = array(
1006
		'Title' => 'Text',
1007
	);
1008
1009
	private static $has_one = array(
1010
		'HasOneFile' => 'File',
1011
		'HasOneFileMaxOne' => 'File',
1012
		'HasOneFileMaxTwo' => 'File',
1013
		'HasOneExtendedFile' => 'UploadFieldTest_ExtendedFile'
1014
	);
1015
1016
	private static $has_many = array(
1017
		'HasManyFiles' => 'File.HasManyRecord',
1018
		'HasManyFilesMaxTwo' => 'File.HasManyMaxTwoRecord',
1019
		'HasManyNoViewFiles' => 'File.HasManyNoViewRecord',
1020
		'ReadonlyField' => 'File.ReadonlyRecord'
1021
	);
1022
1023
	private static $many_many = array(
1024
		'ManyManyFiles' => 'File'
1025
	);
1026
1027
}
1028
1029
class UploadFieldTest_FileExtension extends DataExtension implements TestOnly {
1030
1031
	private static $has_one = array(
1032
		'HasManyRecord' => 'UploadFieldTest_Record',
1033
		'HasManyMaxTwoRecord' => 'UploadFieldTest_Record',
1034
		'HasManyNoViewRecord' => 'UploadFieldTest_Record',
1035
		'ReadonlyRecord' => 'UploadFieldTest_Record'
1036
	);
1037
1038
	private static $has_many = array(
1039
		'HasOneRecords' => 'UploadFieldTest_Record.HasOneFile',
1040
		'HasOneMaxOneRecords' => 'UploadFieldTest_Record.HasOneFileMaxOne',
1041
		'HasOneMaxTwoRecords' => 'UploadFieldTest_Record.HasOneFileMaxTwo',
1042
	);
1043
1044
	private static $belongs_many_many = array(
1045
		'ManyManyRecords' => 'UploadFieldTest_Record'
1046
	);
1047
1048
	public function canDelete($member = null) {
1049
		if($this->owner->Name == 'nodelete.txt') return false;
1050
	}
1051
1052
	public function canEdit($member = null) {
1053
		if($this->owner->Name == 'noedit.txt') return false;
1054
	}
1055
1056
	public function canView($member = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $member is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1057
		if($this->owner->Name == 'noview.txt') return false;
1058
	}
1059
}
1060
1061
/**
1062
 * Used for testing the create-on-upload
1063
 */
1064
class UploadFieldTest_ExtendedFile extends File implements TestOnly {
1065
1066
	private static $has_many = array(
1067
		'HasOneExtendedRecords' => 'UploadFieldTest_Record.HasOneExtendedFile'
1068
	);
1069
}
1070
1071
class UploadFieldTestForm extends Form implements TestOnly {
1072
1073
	public function getRecord() {
1074
		if(empty($this->record)) {
1075
			$this->record = DataObject::get_one('UploadFieldTest_Record', '"Title" = \'Record 1\'');
1076
		}
1077
		return $this->record;
1078
	}
1079
1080
	function __construct($controller = null, $name = 'Form') {
1081
		if(empty($controller)) {
1082
			$controller = new UploadFieldTest_Controller();
1083
		}
1084
1085
		$fieldRootFolder = UploadField::create('RootFolderTest')
1086
			->setFolderName('/');
1087
1088
		$fieldNoRelation = UploadField::create('NoRelationField')
1089
			->setFolderName('UploadedFiles');
1090
1091
		$fieldHasOne = UploadField::create('HasOneFile')
1092
			->setFolderName('UploadedFiles');
1093
1094
		$fieldHasOneExtendedFile = UploadField::create('HasOneExtendedFile')
1095
			->setFolderName('UploadedFiles');
1096
1097
		$fieldHasOneMaxOne = UploadField::create('HasOneFileMaxOne')
1098
			->setFolderName('UploadedFiles')
1099
			->setAllowedMaxFileNumber(1);
1100
1101
		$fieldHasOneMaxTwo = UploadField::create('HasOneFileMaxTwo')
1102
			->setFolderName('UploadedFiles')
1103
			->setAllowedMaxFileNumber(2);
1104
1105
		$fieldHasMany = UploadField::create('HasManyFiles')
1106
			->setFolderName('UploadedFiles');
1107
1108
		$fieldHasManyMaxTwo = UploadField::create('HasManyFilesMaxTwo')
1109
			->setFolderName('UploadedFiles')
1110
			->setAllowedMaxFileNumber(2);
1111
1112
		$fieldManyMany = UploadField::create('ManyManyFiles')
1113
			->setFolderName('UploadedFiles');
1114
1115
		$fieldHasManyNoView = UploadField::create('HasManyNoViewFiles')
1116
			->setFolderName('UploadedFiles');
1117
1118
		$fieldHasManyDisplayFolder = UploadField::create('HasManyDisplayFolder')
1119
			->setFolderName('UploadedFiles')
1120
			->setDisplayFolderName('UploadFieldTest');
1121
1122
		$fieldReadonly = UploadField::create('ReadonlyField')
1123
			->setFolderName('UploadedFiles')
1124
			->performReadonlyTransformation();
1125
1126
		$fieldDisabled = UploadField::create('DisabledField')
1127
			->setFolderName('UploadedFiles')
1128
			->performDisabledTransformation();
1129
1130
		$fieldSubfolder = UploadField::create('SubfolderField')
1131
			->setFolderName('UploadedFiles/subfolder1');
1132
1133
		$fieldCanUploadFalse = UploadField::create('CanUploadFalseField')
1134
			->setCanUpload(false);
1135
1136
		$fieldCanAttachExisting = UploadField::create('CanAttachExistingFalseField')
1137
			->setCanAttachExisting(false);
1138
1139
		$fieldAllowedExtensions = new UploadField('AllowedExtensionsField');
1140
		$fieldAllowedExtensions->getValidator()->setAllowedExtensions(array('txt'));
1141
1142
		$fieldInvalidAllowedExtensions = new UploadField('InvalidAllowedExtensionsField');
1143
		$fieldInvalidAllowedExtensions->getValidator()->setAllowedExtensions(array('txt', 'php'));
1144
1145
		$fields = new FieldList(
1146
			$fieldRootFolder,
1147
			$fieldNoRelation,
1148
			$fieldHasOne,
1149
			$fieldHasOneMaxOne,
1150
			$fieldHasOneMaxTwo,
1151
			$fieldHasOneExtendedFile,
1152
			$fieldHasMany,
1153
			$fieldHasManyMaxTwo,
1154
			$fieldManyMany,
1155
			$fieldHasManyNoView,
1156
			$fieldHasManyDisplayFolder,
1157
			$fieldReadonly,
1158
			$fieldDisabled,
1159
			$fieldSubfolder,
1160
			$fieldCanUploadFalse,
1161
			$fieldCanAttachExisting,
1162
			$fieldAllowedExtensions,
1163
			$fieldInvalidAllowedExtensions
1164
		);
1165
		$actions = new FieldList(
1166
			new FormAction('submit')
1167
		);
1168
		$validator = new RequiredFields();
1169
1170
		parent::__construct($controller, $name, $fields, $actions, $validator);
1171
1172
		$this->loadDataFrom($this->getRecord());
1173
	}
1174
1175
	public function submit($data, Form $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $data is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1176
		$record = $this->getRecord();
1177
		$form->saveInto($record);
1178
		$record->write();
1179
		return json_encode($record->toMap());
1180
	}
1181
}
1182
1183
1184
class UploadFieldTest_Controller extends Controller implements TestOnly {
1185
1186
	protected $template = 'BlankPage';
1187
1188
	private static $allowed_actions = array('Form', 'index', 'submit');
1189
1190
	public function Form() {
1191
		return new UploadFieldTestForm($this, 'Form');
1192
	}
1193
}
1194