mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
6e589aac75
API Implement form schema "errors" handling
1044 lines
39 KiB
PHP
1044 lines
39 KiB
PHP
<?php
|
|
|
|
namespace SilverStripe\Forms\Tests;
|
|
|
|
use SilverStripe\Assets\FileNameFilter;
|
|
use SilverStripe\Control\HTTPResponse;
|
|
use SilverStripe\Forms\Tests\UploadFieldTest\TestController;
|
|
use SilverStripe\Forms\Tests\UploadFieldTest\ExtendedFile;
|
|
use SilverStripe\Forms\Tests\UploadFieldTest\FileExtension;
|
|
use SilverStripe\Forms\Tests\UploadFieldTest\TestRecord;
|
|
use SilverStripe\ORM\Versioning\Versioned;
|
|
use SilverStripe\ORM\DataObject;
|
|
use SilverStripe\ORM\ArrayList;
|
|
use SilverStripe\Assets\Folder;
|
|
use SilverStripe\Assets\Filesystem;
|
|
use SilverStripe\Assets\File;
|
|
use SilverStripe\Assets\Upload;
|
|
use SilverStripe\Core\Config\Config;
|
|
use SilverStripe\Core\Convert;
|
|
use SilverStripe\Dev\CSSContentParser;
|
|
use SilverStripe\Dev\FunctionalTest;
|
|
use SilverStripe\Control\Session;
|
|
use SilverStripe\Control\Controller;
|
|
use SilverStripe\Forms\UploadField;
|
|
use SilverStripe\Forms\FieldList;
|
|
use SilverStripe\Forms\Form;
|
|
use SilverStripe\Assets\Tests\Storage\AssetStoreTest\TestAssetStore;
|
|
|
|
class UploadFieldTest extends FunctionalTest {
|
|
|
|
protected static $fixture_file = 'UploadFieldTest.yml';
|
|
|
|
protected $extraDataObjects = [
|
|
TestRecord::class,
|
|
ExtendedFile::class,
|
|
];
|
|
|
|
protected $extraControllers = [
|
|
TestController::class,
|
|
];
|
|
|
|
protected $requiredExtensions = [
|
|
File::class => [
|
|
FileExtension::class
|
|
]
|
|
];
|
|
|
|
protected $oldReadingMode = null;
|
|
|
|
public function setUp() {
|
|
parent::setUp();
|
|
|
|
$this->logInWithPermission('ADMIN');
|
|
|
|
// Save versioned state
|
|
$this->oldReadingMode = Versioned::get_reading_mode();
|
|
Versioned::set_stage(Versioned::DRAFT);
|
|
|
|
// Set backend root to /UploadFieldTest
|
|
TestAssetStore::activate('UploadFieldTest');
|
|
|
|
// Set the File Name Filter replacements so files have the expected names
|
|
Config::inst()->update(FileNameFilter::class, 'default_replacements', array(
|
|
'/\s/' => '-', // remove whitespace
|
|
'/_/' => '-', // underscores to dashes
|
|
'/[^A-Za-z0-9+.\-]+/' => '', // remove non-ASCII chars, only allow alphanumeric plus dash and dot
|
|
'/[\-]{2,}/' => '-', // remove duplicate dashes
|
|
'/^[\.\-_]+/' => '', // Remove all leading dots, dashes or underscores
|
|
));
|
|
|
|
// Create a test folders for each of the fixture references
|
|
foreach(Folder::get() as $folder) {
|
|
$path = TestAssetStore::getLocalPath($folder);
|
|
Filesystem::makeFolder($path);
|
|
}
|
|
|
|
// Create a test files for each of the fixture references
|
|
$files = File::get()->exclude('ClassName', Folder::class);
|
|
foreach($files as $file) {
|
|
$path = TestAssetStore::getLocalPath($file);
|
|
Filesystem::makeFolder(dirname($path));
|
|
$fh = fopen($path, "w+");
|
|
fwrite($fh, str_repeat('x', 1000000));
|
|
fclose($fh);
|
|
}
|
|
}
|
|
|
|
public function tearDown() {
|
|
TestAssetStore::reset();
|
|
if($this->oldReadingMode) {
|
|
Versioned::set_reading_mode($this->oldReadingMode);
|
|
}
|
|
parent::tearDown();
|
|
}
|
|
|
|
/**
|
|
* Test that files can be uploaded against an object with no relation
|
|
*/
|
|
public function testUploadNoRelation() {
|
|
$tmpFileName = 'testUploadBasic.txt';
|
|
$response = $this->mockFileUpload('NoRelationField', $tmpFileName);
|
|
$this->assertFalse($response->isError());
|
|
$uploadedFile = DataObject::get_one(File::class, array(
|
|
'"File"."Name"' => $tmpFileName
|
|
));
|
|
|
|
$this->assertFileExists(TestAssetStore::getLocalPath($uploadedFile));
|
|
$this->assertTrue(is_object($uploadedFile), 'The file object is created');
|
|
}
|
|
|
|
/**
|
|
* Test that an object can be uploaded against an object with a has_one relation
|
|
*/
|
|
public function testUploadHasOneRelation() {
|
|
// Unset existing has_one relation before re-uploading
|
|
/** @var TestRecord $record */
|
|
$record = $this->objFromFixture(TestRecord::class, 'record1');
|
|
$record->HasOneFileID = null;
|
|
$record->write();
|
|
|
|
// Firstly, ensure the file can be uploaded
|
|
$tmpFileName = 'testUploadHasOneRelation.txt';
|
|
$response = $this->mockFileUpload('HasOneFile', $tmpFileName);
|
|
$this->assertFalse($response->isError());
|
|
$uploadedFile = DataObject::get_one(File::class, array(
|
|
'"File"."Name"' => $tmpFileName
|
|
));
|
|
$this->assertTrue(is_object($uploadedFile), 'The file object is created');
|
|
$this->assertFileExists(TestAssetStore::getLocalPath($uploadedFile));
|
|
|
|
// Secondly, ensure that simply uploading an object does not save the file against the relation
|
|
$record = DataObject::get_by_id($record->class, $record->ID, false);
|
|
$this->assertFalse($record->HasOneFile()->exists());
|
|
|
|
// Thirdly, test submitting the form with the encoded data
|
|
$response = $this->mockUploadFileIDs('HasOneFile', array($uploadedFile->ID));
|
|
$this->assertEmpty($response['errors']);
|
|
$record = DataObject::get_by_id($record->class, $record->ID, false);
|
|
$this->assertTrue($record->HasOneFile()->exists());
|
|
$this->assertEquals($record->HasOneFile()->Name, $tmpFileName);
|
|
}
|
|
|
|
/**
|
|
* Tests that has_one relations work with subclasses of File
|
|
*/
|
|
public function testUploadHasOneRelationWithExtendedFile() {
|
|
// Unset existing has_one relation before re-uploading
|
|
/** @var TestRecord $record */
|
|
$record = $this->objFromFixture(TestRecord::class, 'record1');
|
|
$record->HasOneExtendedFileID = null;
|
|
$record->write();
|
|
|
|
// Test that the file can be safely uploaded
|
|
$tmpFileName = 'testUploadHasOneRelationWithExtendedFile.txt';
|
|
$response = $this->mockFileUpload('HasOneExtendedFile', $tmpFileName);
|
|
$this->assertFalse($response->isError());
|
|
$uploadedFile = DataObject::get_one(ExtendedFile::class, array(
|
|
'"File"."Name"' => $tmpFileName
|
|
));
|
|
$this->assertTrue(is_object($uploadedFile), 'The file object is created');
|
|
$this->assertFileExists(TestAssetStore::getLocalPath($uploadedFile));
|
|
|
|
// Test that the record isn't written to automatically
|
|
$record = DataObject::get_by_id($record->class, $record->ID, false);
|
|
$this->assertFalse($record->HasOneExtendedFile()->exists());
|
|
|
|
// Test that saving the form writes the record
|
|
$response = $this->mockUploadFileIDs('HasOneExtendedFile', array($uploadedFile->ID));
|
|
$this->assertEmpty($response['errors']);
|
|
$record = DataObject::get_by_id($record->class, $record->ID, false);
|
|
$this->assertTrue($record->HasOneExtendedFile()->exists());
|
|
$this->assertEquals($record->HasOneExtendedFile()->Name, $tmpFileName);
|
|
}
|
|
|
|
|
|
/**
|
|
* Test that has_many relations work with files
|
|
*/
|
|
public function testUploadHasManyRelation() {
|
|
$record = $this->objFromFixture(TestRecord::class, 'record1');
|
|
|
|
// Test that uploaded files can be posted to a has_many relation
|
|
$tmpFileName = 'testUploadHasManyRelation.txt';
|
|
$response = $this->mockFileUpload('HasManyFiles', $tmpFileName);
|
|
$this->assertFalse($response->isError());
|
|
$uploadedFile = DataObject::get_one(File::class, array(
|
|
'"File"."Name"' => $tmpFileName
|
|
));
|
|
$this->assertTrue(is_object($uploadedFile), 'The file object is created');
|
|
$this->assertFileExists(TestAssetStore::getLocalPath($uploadedFile));
|
|
|
|
// Test that the record isn't written to automatically
|
|
$record = DataObject::get_by_id($record->class, $record->ID, false);
|
|
$this->assertEquals(2, $record->HasManyFiles()->Count()); // Existing two files should be retained
|
|
|
|
// Test that saving the form writes the record
|
|
$ids = array_merge($record->HasManyFiles()->getIDList(), array($uploadedFile->ID));
|
|
$response = $this->mockUploadFileIDs('HasManyFiles', $ids);
|
|
$this->assertEmpty($response['errors']);
|
|
$record = DataObject::get_by_id($record->class, $record->ID, false);
|
|
$this->assertEquals(3, $record->HasManyFiles()->Count()); // New record should appear here now
|
|
}
|
|
|
|
/**
|
|
* Test that many_many relationships work with files
|
|
*/
|
|
public function testUploadManyManyRelation() {
|
|
$record = $this->objFromFixture(TestRecord::class, 'record1');
|
|
$relationCount = $record->ManyManyFiles()->Count();
|
|
|
|
// Test that uploaded files can be posted to a many_many relation
|
|
$tmpFileName = 'testUploadManyManyRelation.txt';
|
|
$response = $this->mockFileUpload('ManyManyFiles', $tmpFileName);
|
|
$this->assertFalse($response->isError());
|
|
$uploadedFile = DataObject::get_one(File::class, array(
|
|
'"File"."Name"' => $tmpFileName
|
|
));
|
|
$this->assertTrue(is_object($uploadedFile), 'The file object is created');
|
|
$this->assertFileExists(TestAssetStore::getLocalPath($uploadedFile));
|
|
|
|
// Test that the record isn't written to automatically
|
|
$record = DataObject::get_by_id($record->class, $record->ID, false);
|
|
// Existing file count should be retained
|
|
$this->assertEquals($relationCount, $record->ManyManyFiles()->Count());
|
|
|
|
// Test that saving the form writes the record
|
|
$ids = array_merge($record->ManyManyFiles()->getIDList(), array($uploadedFile->ID));
|
|
$response = $this->mockUploadFileIDs('ManyManyFiles', $ids);
|
|
$this->assertEmpty($response['errors']);
|
|
$record = DataObject::get_by_id($record->class, $record->ID, false);
|
|
$record->flushCache();
|
|
// New record should appear here now
|
|
$this->assertEquals($relationCount + 1, $record->ManyManyFiles()->Count());
|
|
}
|
|
|
|
/**
|
|
* Partially covered by {@link UploadTest->testUploadAcceptsAllowedExtension()},
|
|
* but this test additionally verifies that those constraints are actually enforced
|
|
* in this controller method.
|
|
*/
|
|
public function testAllowedExtensions() {
|
|
// Test invalid file
|
|
// Relies on Upload_Validator failing to allow this extension
|
|
$invalidFile = 'invalid.php';
|
|
$_FILES = array('AllowedExtensionsField' => $this->getUploadFile($invalidFile));
|
|
$response = $this->post(
|
|
'UploadFieldTest_Controller/Form/field/AllowedExtensionsField/upload',
|
|
array('AllowedExtensionsField' => $this->getUploadFile($invalidFile))
|
|
);
|
|
$response = json_decode($response->getBody(), true);
|
|
$this->assertTrue(array_key_exists('error', $response[0]));
|
|
$this->assertContains('Extension is not allowed', $response[0]['error']);
|
|
|
|
// Test valid file
|
|
$validFile = 'valid.txt';
|
|
$_FILES = array('AllowedExtensionsField' => $this->getUploadFile($validFile));
|
|
$response = $this->post(
|
|
'UploadFieldTest_Controller/Form/field/AllowedExtensionsField/upload',
|
|
array('AllowedExtensionsField' => $this->getUploadFile($validFile))
|
|
);
|
|
$response = json_decode($response->getBody(), true);
|
|
$this->assertFalse(array_key_exists('error', $response[0]));
|
|
|
|
// Test that setAllowedExtensions rejects extensions explicitly denied by File.allowed_extensions
|
|
// Relies on File::validate failing to allow this extension
|
|
$invalidFile = 'invalid.php';
|
|
$_FILES = array('AllowedExtensionsField' => $this->getUploadFile($invalidFile));
|
|
$response = $this->post(
|
|
'UploadFieldTest_Controller/Form/field/InvalidAllowedExtensionsField/upload',
|
|
array('InvalidAllowedExtensionsField' => $this->getUploadFile($invalidFile))
|
|
);
|
|
$response = json_decode($response->getBody(), true);
|
|
$this->assertTrue(array_key_exists('error', $response[0]));
|
|
$this->assertContains('Extension is not allowed', $response[0]['error']);
|
|
|
|
}
|
|
|
|
/**
|
|
* Test that has_one relations do not support multiple files
|
|
*/
|
|
public function testAllowedMaxFileNumberWithHasOne() {
|
|
// Get references for each file to upload
|
|
$file1 = $this->objFromFixture(File::class, 'file1');
|
|
$file2 = $this->objFromFixture(File::class, 'file2');
|
|
$fileIDs = array($file1->ID, $file2->ID);
|
|
|
|
// Test each of the three cases - has one with no max filel limit, has one with a limit of
|
|
// one, has one with a limit of more than one (makes no sense, but should test it anyway).
|
|
// Each of them should public function in the same way - attaching the first file should work, the
|
|
// second should cause an error.
|
|
foreach (array('HasOneFile', 'HasOneFileMaxOne', 'HasOneFileMaxTwo') as $recordName) {
|
|
|
|
// Unset existing has_one relation before re-uploading
|
|
$record = $this->objFromFixture(TestRecord::class, 'record1');
|
|
$record->{"{$recordName}ID"} = null;
|
|
$record->write();
|
|
|
|
// Post form with two files for this field, should result in an error
|
|
$response = $this->mockUploadFileIDs($recordName, $fileIDs);
|
|
$isError = !empty($response['errors']);
|
|
|
|
// Strictly, a has_one should not allow two files, but this is overridden
|
|
// by the setAllowedMaxFileNumber(2) call
|
|
$maxFiles = ($recordName === 'HasOneFileMaxTwo') ? 2 : 1;
|
|
|
|
// Assert that the form fails if the maximum number of files is exceeded
|
|
$this->assertTrue((count($fileIDs) > $maxFiles) == $isError);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test that max number of items on has_many is validated
|
|
*/
|
|
public function testAllowedMaxFileNumberWithHasMany() {
|
|
// The 'HasManyFilesMaxTwo' field has a maximum of two files able to be attached to it.
|
|
// We want to add files to it until we attempt to add the third. We expect that the first
|
|
// two should work and the third will fail.
|
|
$record = $this->objFromFixture(TestRecord::class, 'record1');
|
|
$record->HasManyFilesMaxTwo()->removeAll();
|
|
$this->assertCount(0, $record->HasManyFilesMaxTwo());
|
|
|
|
// Get references for each file to upload
|
|
$file1 = $this->objFromFixture(File::class, 'file1');
|
|
$file2 = $this->objFromFixture(File::class, 'file2');
|
|
$file3 = $this->objFromFixture(File::class, 'file3');
|
|
$this->assertTrue($file1->exists());
|
|
$this->assertTrue($file2->exists());
|
|
$this->assertTrue($file3->exists());
|
|
|
|
// Write the first element, should be okay.
|
|
$response = $this->mockUploadFileIDs('HasManyFilesMaxTwo', array($file1->ID));
|
|
$this->assertEmpty($response['errors']);
|
|
$this->assertCount(1, $record->HasManyFilesMaxTwo());
|
|
$this->assertContains($file1->ID, $record->HasManyFilesMaxTwo()->getIDList());
|
|
|
|
|
|
$record->HasManyFilesMaxTwo()->removeAll();
|
|
$this->assertCount(0, $record->HasManyFilesMaxTwo());
|
|
$this->assertTrue($file1->exists());
|
|
$this->assertTrue($file2->exists());
|
|
$this->assertTrue($file3->exists());
|
|
|
|
|
|
|
|
// Write the second element, should be okay.
|
|
$response = $this->mockUploadFileIDs('HasManyFilesMaxTwo', array($file1->ID, $file2->ID));
|
|
$this->assertEmpty($response['errors']);
|
|
$this->assertCount(2, $record->HasManyFilesMaxTwo());
|
|
$this->assertContains($file1->ID, $record->HasManyFilesMaxTwo()->getIDList());
|
|
$this->assertContains($file2->ID, $record->HasManyFilesMaxTwo()->getIDList());
|
|
|
|
$record->HasManyFilesMaxTwo()->removeAll();
|
|
$this->assertCount(0, $record->HasManyFilesMaxTwo());
|
|
$this->assertTrue($file1->exists());
|
|
$this->assertTrue($file2->exists());
|
|
$this->assertTrue($file3->exists());
|
|
|
|
|
|
// Write the third element, should result in error.
|
|
$response = $this->mockUploadFileIDs('HasManyFilesMaxTwo', array($file1->ID, $file2->ID, $file3->ID));
|
|
$this->assertNotEmpty($response['errors']);
|
|
$this->assertCount(0, $record->HasManyFilesMaxTwo());
|
|
}
|
|
|
|
/**
|
|
* Test that files can be removed from has_one relations
|
|
*/
|
|
public function testRemoveFromHasOne() {
|
|
$record = $this->objFromFixture(TestRecord::class, 'record1');
|
|
$file1 = $this->objFromFixture(File::class, 'file1');
|
|
|
|
// Check record exists
|
|
$this->assertTrue($record->HasOneFile()->exists());
|
|
|
|
// Remove from record
|
|
$response = $this->mockUploadFileIDs('HasOneFile', array());
|
|
$this->assertEmpty($response['errors']);
|
|
|
|
// Check file is removed
|
|
$record = DataObject::get_by_id($record->class, $record->ID, false);
|
|
$this->assertFalse($record->HasOneFile()->exists());
|
|
|
|
// Check file object itself exists
|
|
$this->assertFileExists(
|
|
TestAssetStore::getLocalPath($file1),
|
|
'File is only detached, not deleted from filesystem'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Test that items can be removed from has_many
|
|
*/
|
|
public function testRemoveFromHasMany() {
|
|
$record = $this->objFromFixture(TestRecord::class, 'record1');
|
|
$file3 = $this->objFromFixture(File::class, 'file3');
|
|
|
|
// Check record has two files attached
|
|
$this->assertEquals(array('File2', 'File3'), $record->HasManyFiles()->column('Title'));
|
|
|
|
// Remove file 2
|
|
$response = $this->mockUploadFileIDs('HasManyFiles', array($file3->ID));
|
|
$this->assertEmpty($response['errors']);
|
|
|
|
// check only file 3 is left
|
|
$record = DataObject::get_by_id($record->class, $record->ID, false);
|
|
$this->assertEquals(array('File3'), $record->HasManyFiles()->column('Title'));
|
|
|
|
// Check file 2 object itself exists
|
|
$this->assertFileExists(
|
|
TestAssetStore::getLocalPath($file3),
|
|
'File is only detached, not deleted from filesystem'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Test that items can be removed from many_many
|
|
*/
|
|
public function testRemoveFromManyMany() {
|
|
$record = $this->objFromFixture(TestRecord::class, 'record1');
|
|
$file4 = $this->objFromFixture(File::class, 'file4');
|
|
$file5 = $this->objFromFixture(File::class, 'file5');
|
|
|
|
// Check that both files are currently set
|
|
$this->assertContains('File4', $record->ManyManyFiles()->column('Title'));
|
|
$this->assertContains('File5', $record->ManyManyFiles()->column('Title'));
|
|
|
|
// Remove file 4
|
|
$response = $this->mockUploadFileIDs('ManyManyFiles', array($file5->ID));
|
|
$this->assertEmpty($response['errors']);
|
|
|
|
// check only file 5 is left
|
|
$record = DataObject::get_by_id($record->class, $record->ID, false);
|
|
$this->assertNotContains('File4', $record->ManyManyFiles()->column('Title'));
|
|
$this->assertContains('File5', $record->ManyManyFiles()->column('Title'));
|
|
|
|
// check file 4 object exists
|
|
$this->assertFileExists(
|
|
TestAssetStore::getLocalPath($file4),
|
|
'File is only detached, not deleted from filesystem'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Test that files can be deleted from has_one
|
|
*/
|
|
public function testDeleteFromHasOne() {
|
|
$record = $this->objFromFixture(TestRecord::class, 'record1');
|
|
$file1 = $this->objFromFixture(File::class, 'file1');
|
|
|
|
// Check that file initially exists
|
|
$this->assertTrue($record->HasOneFile()->exists());
|
|
$this->assertFileExists(TestAssetStore::getLocalPath($file1));
|
|
|
|
// Delete file and update record
|
|
$response = $this->mockFileDelete('HasOneFile', $file1->ID);
|
|
$this->assertFalse($response->isError());
|
|
$response = $this->mockUploadFileIDs('HasOneFile', array());
|
|
$this->assertEmpty($response['errors']);
|
|
|
|
// Check that file is not set against record
|
|
$record = DataObject::get_by_id($record->class, $record->ID, false);
|
|
$this->assertFalse($record->HasOneFile()->exists());
|
|
}
|
|
|
|
/**
|
|
* Test that files can be deleted from has_many
|
|
*/
|
|
public function testDeleteFromHasMany() {
|
|
$record = $this->objFromFixture(TestRecord::class, 'record1');
|
|
$file2 = $this->objFromFixture(File::class, 'file2');
|
|
$file3 = $this->objFromFixture(File::class, 'file3');
|
|
|
|
// Check that files initially exists
|
|
$this->assertEquals(array('File2', 'File3'), $record->HasManyFiles()->column('Title'));
|
|
$this->assertFileExists(TestAssetStore::getLocalPath($file2));
|
|
$this->assertFileExists(TestAssetStore::getLocalPath($file3));
|
|
|
|
// Delete dataobject file and update record without file 2
|
|
$response = $this->mockFileDelete('HasManyFiles', $file2->ID);
|
|
$this->assertFalse($response->isError());
|
|
$response = $this->mockUploadFileIDs('HasManyFiles', array($file3->ID));
|
|
$this->assertEmpty($response['errors']);
|
|
|
|
// Test that file is removed from record
|
|
$record = DataObject::get_by_id($record->class, $record->ID, false);
|
|
$this->assertEquals(array('File3'), $record->HasManyFiles()->column('Title'));
|
|
}
|
|
|
|
/**
|
|
* Test that files can be deleted from many_many and the filesystem
|
|
*/
|
|
public function testDeleteFromManyMany() {
|
|
$record = $this->objFromFixture(TestRecord::class, 'record1');
|
|
$file4 = $this->objFromFixture(File::class, 'file4');
|
|
$file5 = $this->objFromFixture(File::class, 'file5');
|
|
$fileNoDelete = $this->objFromFixture(File::class, 'file-nodelete');
|
|
|
|
// Test that files initially exist
|
|
$setFiles = $record->ManyManyFiles()->column('Title');
|
|
$this->assertContains('File4', $setFiles);
|
|
$this->assertContains('File5', $setFiles);
|
|
$this->assertContains('nodelete.txt', $setFiles);
|
|
$this->assertFileExists(TestAssetStore::getLocalPath($file4));
|
|
$this->assertFileExists(TestAssetStore::getLocalPath($file5));
|
|
$this->assertFileExists(TestAssetStore::getLocalPath($fileNoDelete));
|
|
|
|
// Delete physical file and update record without file 4
|
|
$response = $this->mockFileDelete('ManyManyFiles', $file4->ID);
|
|
$this->assertFalse($response->isError());
|
|
|
|
// Check file is removed from record
|
|
$record = DataObject::get_by_id($record->class, $record->ID, false);
|
|
$this->assertNotContains('File4', $record->ManyManyFiles()->column('Title'));
|
|
$this->assertContains('File5', $record->ManyManyFiles()->column('Title'));
|
|
|
|
// Test record-based permissions
|
|
$response = $this->mockFileDelete('ManyManyFiles', $fileNoDelete->ID);
|
|
$this->assertEquals(403, $response->getStatusCode());
|
|
|
|
// Test that folders can't be deleted
|
|
$folder = $this->objFromFixture(Folder::class, 'folder1-subfolder1');
|
|
$response = $this->mockFileDelete('ManyManyFiles', $folder->ID);
|
|
$this->assertEquals(403, $response->getStatusCode());
|
|
}
|
|
|
|
/**
|
|
* Test control output html
|
|
*/
|
|
public function testView() {
|
|
$file4 = $this->objFromFixture(File::class, 'file4');
|
|
$file5 = $this->objFromFixture(File::class, 'file5');
|
|
$fileNoView = $this->objFromFixture(File::class, 'file-noview');
|
|
$fileNoEdit = $this->objFromFixture(File::class, 'file-noedit');
|
|
$fileNoDelete = $this->objFromFixture(File::class, 'file-nodelete');
|
|
|
|
$response = $this->get('UploadFieldTest_Controller');
|
|
$this->assertFalse($response->isError());
|
|
|
|
$parser = new CSSContentParser($response->getBody());
|
|
$items = $parser->getBySelector(
|
|
'#UploadFieldTestForm_Form_HasManyNoViewFiles_Holder .ss-uploadfield-files .ss-uploadfield-item'
|
|
);
|
|
$ids = array();
|
|
foreach($items as $item) $ids[] = (int)$item['data-fileid'];
|
|
|
|
$this->assertContains($file4->ID, $ids, 'Views related file');
|
|
$this->assertContains($file5->ID, $ids, 'Views related file');
|
|
$this->assertNotContains($fileNoView->ID, $ids, "Doesn't view files without view permissions");
|
|
$this->assertContains($fileNoEdit->ID, $ids, "Views files without edit permissions");
|
|
$this->assertContains($fileNoDelete->ID, $ids, "Views files without delete permissions");
|
|
}
|
|
|
|
public function testEdit() {
|
|
//for some reason the date_format is being set to null
|
|
Config::inst()->update('i18n', 'date_format', 'yyyy-MM-dd');
|
|
$memberID = $this->loginWithPermission('ADMIN');
|
|
$record = $this->objFromFixture(TestRecord::class, 'record1');
|
|
$file4 = $this->objFromFixture(File::class, 'file4');
|
|
$fileNoEdit = $this->objFromFixture(File::class, 'file-noedit');
|
|
$folder = $this->objFromFixture(Folder::class, 'folder1-subfolder1');
|
|
|
|
$response = $this->mockFileEditForm('ManyManyFiles', $file4->ID);
|
|
$this->assertFalse($response->isError());
|
|
|
|
$response = $this->mockFileEdit('ManyManyFiles', $file4->ID, array('Title' => 'File 4 modified'));
|
|
$this->assertFalse($response->isError());
|
|
|
|
$file4 = DataObject::get_by_id($file4->class, $file4->ID, false);
|
|
$this->assertEquals('File 4 modified', $file4->Title);
|
|
|
|
// Test record-based permissions
|
|
$response = $this->mockFileEditForm('ManyManyFiles', $fileNoEdit->ID);
|
|
$this->assertEquals(403, $response->getStatusCode());
|
|
|
|
$response = $this->mockFileEdit('ManyManyFiles', $fileNoEdit->ID, array());
|
|
$this->assertEquals(403, $response->getStatusCode());
|
|
|
|
// Test folder permissions
|
|
$response = $this->mockFileEditForm('ManyManyFiles', $folder->ID);
|
|
$this->assertEquals(403, $response->getStatusCode());
|
|
|
|
$response = $this->mockFileEdit('ManyManyFiles', $folder->ID, array());
|
|
$this->assertEquals(403, $response->getStatusCode());
|
|
}
|
|
|
|
public function testGetRecord() {
|
|
$record = $this->objFromFixture(TestRecord::class, 'record1');
|
|
$form = $this->getMockForm();
|
|
|
|
$field = UploadField::create('MyField');
|
|
$field->setForm($form);
|
|
$this->assertNull($field->getRecord(), 'Returns no record by default');
|
|
|
|
$field = UploadField::create('MyField');
|
|
$field->setForm($form);
|
|
$form->loadDataFrom($record);
|
|
$this->assertEquals($record, $field->getRecord(), 'Returns record from form if available');
|
|
|
|
$field = UploadField::create('MyField');
|
|
$field->setForm($form);
|
|
$field->setRecord($record);
|
|
$this->assertEquals($record, $field->getRecord(), 'Returns record when set explicitly');
|
|
}
|
|
|
|
public function testSetItems() {
|
|
$record = $this->objFromFixture(TestRecord::class, 'record1');
|
|
$items = new ArrayList(array(
|
|
$this->objFromFixture(File::class, 'file1'),
|
|
$this->objFromFixture(File::class, 'file2')
|
|
));
|
|
|
|
// Field with no record attached
|
|
$field = UploadField::create('DummyField');
|
|
$field->setItems($items);
|
|
$this->assertEquals(array('File1', 'File2'), $field->getItems()->column('Title'));
|
|
|
|
// Anonymous field
|
|
$field = UploadField::create('MyField');
|
|
$field->setRecord($record);
|
|
$field->setItems($items);
|
|
$this->assertEquals(array('File1', 'File2'), $field->getItems()->column('Title'));
|
|
|
|
// Field with has_one auto-detected
|
|
$field = UploadField::create('HasOneFile');
|
|
$field->setRecord($record);
|
|
$field->setItems($items);
|
|
$this->assertEquals(array('File1', 'File2'), $field->getItems()->column('Title'),
|
|
'Allows overwriting of items even when relationship is detected'
|
|
);
|
|
}
|
|
|
|
public function testGetItems() {
|
|
$record = $this->objFromFixture(TestRecord::class, 'record1');
|
|
|
|
// Anonymous field
|
|
$field = UploadField::create('MyField');
|
|
$field->setValue(null, $record);
|
|
$this->assertEquals(array(), $field->getItems()->column('Title'));
|
|
|
|
// Field with has_one auto-detected
|
|
$field = UploadField::create('HasOneFile');
|
|
$field->setValue(null, $record);
|
|
$this->assertEquals(array('File1'), $field->getItems()->column('Title'));
|
|
|
|
// Field with has_many auto-detected
|
|
$field = UploadField::create('HasManyFiles');
|
|
$field->setValue(null, $record);
|
|
$this->assertEquals(array('File2', 'File3'), $field->getItems()->column('Title'));
|
|
|
|
// Field with many_many auto-detected
|
|
$field = UploadField::create('ManyManyFiles');
|
|
$field->setValue(null, $record);
|
|
$this->assertNotContains('File1',$field->getItems()->column('Title'));
|
|
$this->assertNotContains('File2',$field->getItems()->column('Title'));
|
|
$this->assertNotContains('File3',$field->getItems()->column('Title'));
|
|
$this->assertContains('File4',$field->getItems()->column('Title'));
|
|
$this->assertContains('File5',$field->getItems()->column('Title'));
|
|
}
|
|
|
|
public function testReadonly() {
|
|
$response = $this->get('UploadFieldTest_Controller');
|
|
$this->assertFalse($response->isError());
|
|
|
|
$parser = new CSSContentParser($response->getBody());
|
|
|
|
$this->assertFalse(
|
|
(bool)$parser->getBySelector(
|
|
'#UploadFieldTestForm_Form_ReadonlyField .ss-uploadfield-files .ss-uploadfield-item .ss-ui-button'
|
|
),
|
|
'Removes all buttons on items');
|
|
$this->assertFalse(
|
|
(bool)$parser->getBySelector('#UploadFieldTestForm_Form_ReadonlyField .ss-uploadfield-dropzone'),
|
|
'Removes dropzone'
|
|
);
|
|
$this->assertFalse(
|
|
(bool)$parser->getBySelector(
|
|
'#UploadFieldTestForm_Form_ReadonlyField .ss-uploadfield-addfile'
|
|
),
|
|
'Entire "add" area'
|
|
);
|
|
}
|
|
|
|
public function testDisabled() {
|
|
$response = $this->get('UploadFieldTest_Controller');
|
|
$this->assertFalse($response->isError());
|
|
|
|
$parser = new CSSContentParser($response->getBody());
|
|
$this->assertFalse(
|
|
(bool)$parser->getBySelector(
|
|
'#UploadFieldTestForm_Form_DisabledField .ss-uploadfield-files .ss-uploadfield-item .ss-ui-button'
|
|
),
|
|
'Removes all buttons on items');
|
|
$this->assertFalse((bool)$parser->getBySelector(
|
|
'#UploadFieldTestForm_Form_DisabledField .ss-uploadfield-dropzone'
|
|
),
|
|
'Removes dropzone');
|
|
$this->assertFalse(
|
|
(bool)$parser->getBySelector('#UploadFieldTestForm_Form_DisabledField .ss-uploadfield-addfile'),
|
|
'Entire "add" area'
|
|
);
|
|
}
|
|
|
|
public function testCanUpload() {
|
|
$response = $this->get('UploadFieldTest_Controller');
|
|
$this->assertFalse($response->isError());
|
|
|
|
$parser = new CSSContentParser($response->getBody());
|
|
$this->assertFalse(
|
|
(bool)$parser->getBySelector(
|
|
'#UploadFieldTestForm_Form_CanUploadFalseField_Holder .ss-uploadfield-dropzone'
|
|
),
|
|
'Removes dropzone');
|
|
$this->assertTrue(
|
|
(bool)$parser->getBySelector(
|
|
'#UploadFieldTestForm_Form_CanUploadFalseField_Holder .ss-uploadfield-fromfiles'
|
|
),
|
|
'Keeps "From files" button'
|
|
);
|
|
}
|
|
|
|
public function testCanUploadWithPermissionCode() {
|
|
$field = UploadField::create('MyField');
|
|
Session::clear("loggedInAs");
|
|
|
|
$field->setCanUpload(true);
|
|
$this->assertTrue($field->canUpload());
|
|
|
|
$field->setCanUpload(false);
|
|
$this->assertFalse($field->canUpload());
|
|
|
|
$this->logInWithPermission('ADMIN');
|
|
|
|
$field->setCanUpload(false);
|
|
$this->assertFalse($field->canUpload());
|
|
|
|
$field->setCanUpload('ADMIN');
|
|
$this->assertTrue($field->canUpload());
|
|
}
|
|
|
|
public function testCanAttachExisting() {
|
|
$response = $this->get('UploadFieldTest_Controller');
|
|
$this->assertFalse($response->isError());
|
|
|
|
$parser = new CSSContentParser($response->getBody());
|
|
$this->assertTrue(
|
|
(bool)$parser->getBySelector(
|
|
'#UploadFieldTestForm_Form_CanAttachExistingFalseField_Holder .ss-uploadfield-fromcomputer-fileinput'
|
|
),
|
|
'Keeps input file control'
|
|
);
|
|
$this->assertFalse(
|
|
(bool)$parser->getBySelector(
|
|
'#UploadFieldTestForm_Form_CanAttachExistingFalseField_Holder .ss-uploadfield-fromfiles'
|
|
),
|
|
'Removes "From files" button'
|
|
);
|
|
|
|
// Test requests to select files have the correct given permission
|
|
$response2 = $this->get('UploadFieldTest_Controller/Form/field/CanAttachExistingFalseField/select');
|
|
$this->assertEquals(403, $response2->getStatusCode());
|
|
$response3 = $this->get('UploadFieldTest_Controller/Form/field/HasOneFile/select');
|
|
$this->assertEquals(200, $response3->getStatusCode());
|
|
}
|
|
|
|
public function testSelect() {
|
|
$file4 = $this->objFromFixture(File::class, 'file4');
|
|
$fileSubfolder = $this->objFromFixture(File::class, 'file-subfolder');
|
|
|
|
$response = $this->get('UploadFieldTest_Controller/Form/field/ManyManyFiles/select/');
|
|
$this->assertFalse($response->isError());
|
|
|
|
// A bit too much coupling with GridField, but a full template overload would make things too complex
|
|
$parser = new CSSContentParser($response->getBody());
|
|
$items = $parser->getBySelector('.ss-gridfield-item');
|
|
$itemIDs = array_map(create_function('$el', 'return (int)$el["data-id"];'), $items);
|
|
$this->assertContains($file4->ID, $itemIDs, 'Contains file in assigned folder');
|
|
$this->assertContains($fileSubfolder->ID, $itemIDs, 'Contains file in subfolder');
|
|
}
|
|
|
|
public function testSelectWithDisplayFolderName() {
|
|
$file4 = $this->objFromFixture(File::class, 'file4');
|
|
$fileSubfolder = $this->objFromFixture(File::class, 'file-subfolder');
|
|
|
|
$response = $this->get('UploadFieldTest_Controller/Form/field/HasManyDisplayFolder/select/');
|
|
$this->assertFalse($response->isError());
|
|
|
|
// A bit too much coupling with GridField, but a full template overload would make things too complex
|
|
$parser = new CSSContentParser($response->getBody());
|
|
$items = $parser->getBySelector('.ss-gridfield-item');
|
|
$itemIDs = array_map(create_function('$el', 'return (int)$el["data-id"];'), $items);
|
|
$this->assertContains($file4->ID, $itemIDs, 'Contains file in assigned folder');
|
|
$this->assertNotContains($fileSubfolder->ID, $itemIDs, 'Does not contain file in subfolder');
|
|
}
|
|
|
|
/**
|
|
* Test that UploadField:overwriteWarning cannot overwrite Upload:replaceFile
|
|
*/
|
|
public function testConfigOverwriteWarningCannotRelaceFiles() {
|
|
Upload::config()->replaceFile = false;
|
|
UploadField::config()->defaultConfig = array_merge(
|
|
UploadField::config()->defaultConfig, array('overwriteWarning' => true)
|
|
);
|
|
|
|
$tmpFileName = 'testUploadBasic.txt';
|
|
$response = $this->mockFileUpload('NoRelationField', $tmpFileName);
|
|
$this->assertFalse($response->isError());
|
|
$responseData = Convert::json2array($response->getBody());
|
|
$uploadedFile = DataObject::get_by_id(File::class, (int) $responseData[0]['id']);
|
|
$this->assertTrue(is_object($uploadedFile), 'The file object is created');
|
|
$this->assertFileExists(TestAssetStore::getLocalPath($uploadedFile));
|
|
|
|
$tmpFileName = 'testUploadBasic.txt';
|
|
$response = $this->mockFileUpload('NoRelationField', $tmpFileName);
|
|
$this->assertFalse($response->isError());
|
|
$responseData = Convert::json2array($response->getBody());
|
|
$uploadedFile2 = DataObject::get_by_id(File::class, (int) $responseData[0]['id']);
|
|
$this->assertTrue(is_object($uploadedFile2), 'The file object is created');
|
|
$this->assertFileExists(TestAssetStore::getLocalPath($uploadedFile2));
|
|
$this->assertTrue(
|
|
$uploadedFile->Filename !== $uploadedFile2->Filename,
|
|
'Filename is not the same'
|
|
);
|
|
$this->assertTrue(
|
|
$uploadedFile->ID !== $uploadedFile2->ID,
|
|
'File database record is not the same'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Tests that UploadField::fileexist works
|
|
*/
|
|
public function testFileExists() {
|
|
// Check that fileexist works on subfolders
|
|
$nonFile = uniqid().'.txt';
|
|
$responseEmpty = $this->mockFileExists('NoRelationField', $nonFile);
|
|
$responseEmptyData = json_decode($responseEmpty->getBody());
|
|
$this->assertFalse($responseEmpty->isError());
|
|
$this->assertFalse($responseEmptyData->exists);
|
|
|
|
// Check that filexists works on root folder
|
|
$responseRoot = $this->mockFileExists('RootFolderTest', $nonFile);
|
|
$responseRootData = json_decode($responseRoot->getBody());
|
|
$this->assertFalse($responseRoot->isError());
|
|
$this->assertFalse($responseRootData->exists);
|
|
|
|
// Check that uploaded files can be detected in the root
|
|
$tmpFileName = 'testUploadBasic.txt';
|
|
$response = $this->mockFileUpload('RootFolderTest', $tmpFileName);
|
|
$this->assertFalse($response->isError());
|
|
$this->assertFileExists(ASSETS_PATH . "/UploadFieldTest/.protected/315ae4c3d4/$tmpFileName");
|
|
$responseExists = $this->mockFileExists('RootFolderTest', $tmpFileName);
|
|
$responseExistsData = json_decode($responseExists->getBody());
|
|
$this->assertFalse($responseExists->isError());
|
|
$this->assertTrue($responseExistsData->exists);
|
|
|
|
// Check that uploaded files can be detected
|
|
$response = $this->mockFileUpload('NoRelationField', $tmpFileName);
|
|
$this->assertFalse($response->isError());
|
|
$this->assertFileExists(ASSETS_PATH . "/UploadFieldTest/.protected/UploadedFiles/315ae4c3d4/$tmpFileName");
|
|
$responseExists = $this->mockFileExists('NoRelationField', $tmpFileName);
|
|
$responseExistsData = json_decode($responseExists->getBody());
|
|
$this->assertFalse($responseExists->isError());
|
|
$this->assertTrue($responseExistsData->exists);
|
|
|
|
// Test that files with invalid characters are rewritten safely and both report exists
|
|
// Check that uploaded files can be detected in the root
|
|
$tmpFileName = '_test___Upload___Bad.txt';
|
|
$tmpFileNameExpected = 'test-Upload-Bad.txt';
|
|
$response = $this->mockFileUpload('NoRelationField', $tmpFileName);
|
|
$this->assertFalse($response->isError());
|
|
$this->assertFileExists(ASSETS_PATH . "/UploadFieldTest/.protected/UploadedFiles/315ae4c3d4/$tmpFileNameExpected");
|
|
// With original file
|
|
$responseExists = $this->mockFileExists('NoRelationField', $tmpFileName);
|
|
$responseExistsData = json_decode($responseExists->getBody());
|
|
$this->assertFalse($responseExists->isError());
|
|
$this->assertTrue($responseExistsData->exists);
|
|
// With rewritten file
|
|
$responseExists = $this->mockFileExists('NoRelationField', $tmpFileNameExpected);
|
|
$responseExistsData = json_decode($responseExists->getBody());
|
|
$this->assertFalse($responseExists->isError());
|
|
$this->assertTrue($responseExistsData->exists);
|
|
|
|
// Test that attempts to navigate outside of the directory return false
|
|
$responseExists = $this->mockFileExists('NoRelationField', "../../../../var/private/$tmpFileName");
|
|
$this->assertTrue($responseExists->isError());
|
|
$this->assertContains('File is not a valid upload', $responseExists->getBody());
|
|
}
|
|
|
|
protected function getMockForm() {
|
|
/** @skipUpgrade */
|
|
return new Form(new Controller(), 'Form', new FieldList(), new FieldList());
|
|
}
|
|
|
|
/**
|
|
* @param string $tmpFileName
|
|
* @return array Emulating an entry in the $_FILES superglobal
|
|
*/
|
|
protected function getUploadFile($tmpFileName = 'UploadFieldTest-testUpload.txt') {
|
|
$tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName;
|
|
$tmpFileContent = '';
|
|
for($i=0; $i<10000; $i++) $tmpFileContent .= '0';
|
|
file_put_contents($tmpFilePath, $tmpFileContent);
|
|
|
|
// emulates the $_FILES array
|
|
return array(
|
|
'name' => array('Uploads' => array($tmpFileName)),
|
|
'type' => array('Uploads' => array('text/plaintext')),
|
|
'size' => array('Uploads' => array(filesize($tmpFilePath))),
|
|
'tmp_name' => array('Uploads' => array($tmpFilePath)),
|
|
'error' => array('Uploads' => array(UPLOAD_ERR_OK)),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Simulates a form post to the test controller with the specified file IDs
|
|
*
|
|
* @param string $fileField Name of field to assign ids to
|
|
* @param array $ids list of file IDs
|
|
* @return array Array with key 'errors'
|
|
*/
|
|
protected function mockUploadFileIDs($fileField, $ids) {
|
|
|
|
// collate file ids
|
|
$files = array();
|
|
foreach($ids as $id) {
|
|
$files[$id] = $id;
|
|
}
|
|
|
|
$data = array(
|
|
'action_submit' => 1
|
|
);
|
|
if($files) {
|
|
// Normal post requests can't submit empty array values for fields
|
|
$data[$fileField] = array('Files' => $files);
|
|
}
|
|
|
|
$form = new UploadFieldTest\UploadFieldTestForm();
|
|
$form->loadDataFrom($data, true);
|
|
|
|
if($form->validationResult()->isValid()) {
|
|
$record = $form->getRecord();
|
|
$form->saveInto($record);
|
|
$record->write();
|
|
return array('errors' => null);
|
|
} else {
|
|
return array('errors' => $form->getValidator()->getErrors());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Simulates a file upload
|
|
*
|
|
* @param string $fileField Name of the field to mock upload for
|
|
* @param array $tmpFileName Name of temporary file to upload
|
|
* @return HTTPResponse form response
|
|
*/
|
|
protected function mockFileUpload($fileField, $tmpFileName) {
|
|
$upload = $this->getUploadFile($tmpFileName);
|
|
$_FILES = array($fileField => $upload);
|
|
return $this->post(
|
|
"UploadFieldTest_Controller/Form/field/{$fileField}/upload",
|
|
array($fileField => $upload)
|
|
);
|
|
}
|
|
|
|
protected function mockFileExists($fileField, $fileName) {
|
|
return $this->get(
|
|
"UploadFieldTest_Controller/Form/field/{$fileField}/fileexists?filename=".urlencode($fileName)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Gets the edit form for the given file
|
|
*
|
|
* @param string $fileField Name of the field
|
|
* @param integer $fileID ID of the file to delete
|
|
* @return HTTPResponse form response
|
|
*/
|
|
protected function mockFileEditForm($fileField, $fileID) {
|
|
return $this->get(
|
|
"UploadFieldTest_Controller/Form/field/{$fileField}/item/{$fileID}/edit"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Mocks edit submissions to a file
|
|
*
|
|
* @param string $fileField Name of the field
|
|
* @param integer $fileID ID of the file to delete
|
|
* @param array $fields Fields to update
|
|
* @return HTTPResponse form response
|
|
*/
|
|
protected function mockFileEdit($fileField, $fileID, $fields = array()) {
|
|
return $this->post(
|
|
"UploadFieldTest_Controller/Form/field/{$fileField}/item/{$fileID}/EditForm",
|
|
$fields
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Simulates a physical file deletion
|
|
*
|
|
* @param string $fileField Name of the field
|
|
* @param integer $fileID ID of the file to delete
|
|
* @return HTTPResponse form response
|
|
*/
|
|
protected function mockFileDelete($fileField, $fileID) {
|
|
return $this->post(
|
|
"UploadFieldTest_Controller/Form/field/{$fileField}/item/{$fileID}/delete",
|
|
array()
|
|
);
|
|
}
|
|
|
|
public function get($url, $session = null, $headers = null, $cookies = null) {
|
|
// Inject stage=Stage into the URL, to force working on draft
|
|
$url = $this->addStageToUrl($url);
|
|
return parent::get($url, $session, $headers, $cookies);
|
|
}
|
|
|
|
public function post($url, $data, $headers = null, $session = null, $body = null, $cookies = null) {
|
|
// Inject stage=Stage into the URL, to force working on draft
|
|
$url = $this->addStageToUrl($url);
|
|
return parent::post($url, $data, $headers, $session, $body, $cookies);
|
|
}
|
|
|
|
/**
|
|
* Adds ?stage=Stage to url
|
|
*
|
|
* @param string $url
|
|
* @return string
|
|
*/
|
|
protected function addStageToUrl($url) {
|
|
if(stripos($url, 'stage=Stage') === false) {
|
|
if(stripos($url, '?') === false) {
|
|
$url .= '?stage=Stage';
|
|
} else {
|
|
$url .= '&stage=Stage';
|
|
}
|
|
}
|
|
return $url;
|
|
}
|
|
|
|
}
|