ENHANCEMENT Added File::get_class_for_file_extension() instead of hardcoding it in Folder->constructChild(). Allows for custom classes in files uploaded through core functionality like the Upload and FileField logic.

This commit is contained in:
Ingo Schommer 2011-08-22 13:04:15 +02:00
parent 3bd80dfb87
commit afeccbc9cb
5 changed files with 101 additions and 12 deletions

View File

@ -790,6 +790,53 @@ class File extends DataObject {
return $fields;
}
}
/**
* @var Array Only use lowercase extensions in here.
*/
static $class_for_file_extension = array(
'*' => 'File',
'jpg' => 'Image',
'jpeg' => 'Image',
'png' => 'Image',
'gif' => 'Image',
);
?>
/**
* Maps a {@link File} subclass to a specific extension.
* By default, files with common image extensions will be created
* as {@link Image} instead of {@link File} when using
* {@link Folder::constructChild}, {@link Folder::addUploadToFolder}),
* and the {@link Upload} class (either directly or through {@link FileField}).
* For manually instanciated files please use this mapping getter.
*
* Caution: Changes to mapping doesn't apply to existing file records in the database.
* Also doesn't hook into {@link Object::getCustomClass()}.
*
* @param String File extension, without dot prefix. Use an asterisk ('*')
* to specify a generic fallback if no mapping is found for an extension.
* @return String Classname for a subclass of {@link File}
*/
static function get_class_for_file_extension($ext) {
$map = array_change_key_case(self::$class_for_file_extension, CASE_LOWER);
return (array_key_exists(strtolower($ext), $map)) ? $map[strtolower($ext)] : $map['*'];
}
/**
* See {@link get_class_for_file_extension()}.
*
* @param String|array
* @param String
*/
static function set_class_for_file_extension($exts, $class) {
if(!is_array($exts)) $exts = array($exts);
foreach($exts as $ext) {
if(ClassInfo::is_subclass_of($ext, 'File')) {
throw new InvalidArgumentException(
sprintf('Class "%s" (for extension "%s") is not a valid subclass of File', $class, $ext)
);
}
self::$class_for_file_extension[$ext] = $class;
}
}
}

View File

@ -183,12 +183,7 @@ class Folder extends File {
if(is_dir($baseDir . $name)) {
$className = "Folder";
} else {
// Could use getimagesize to get the type of the image
$ext = strtolower(substr($name,strrpos($name,'.')+1));
switch($ext) {
case "gif": case "jpg": case "jpeg": case "png": $className = "Image"; break;
default: $className = "File";
}
$className = File::get_class_for_file_extension(pathinfo($name, PATHINFO_EXTENSION));
}
if(Member::currentUser()) $ownerID = Member::currentUser()->ID;

View File

@ -98,7 +98,10 @@ class Upload extends Controller {
if(!$folderPath) $folderPath = self::$uploads_folder;
if(!$this->file) $this->file = new File();
if(!$this->file) {
$fileClass = File::get_class_for_file_extension(pathinfo($tmpFile['name'], PATHINFO_EXTENSION));
$this->file = new $fileClass();
}
if(!is_array($tmpFile)) {
user_error("Upload::load() Not passed an array. Most likely, the form hasn't got the right enctype", E_USER_ERROR);

View File

@ -133,14 +133,15 @@ class FileField extends FormField {
public function saveInto(DataObject $record) {
if(!isset($_FILES[$this->name])) return false;
$fileClass = File::get_class_for_file_extension(pathinfo($_FILES[$this->name]['name'], PATHINFO_EXTENSION));
if($this->relationAutoSetting) {
// assume that the file is connected via a has-one
$hasOnes = $record->has_one($this->name);
// try to create a file matching the relation
$file = (is_string($hasOnes)) ? Object::create($hasOnes) : new File();
$file = (is_string($hasOnes)) ? Object::create($hasOnes) : new $fileClass();
} else {
$file = new File();
$file = new $fileClass();
}
$this->upload->loadIntoFile($_FILES[$this->name], $file, $this->folderName);
@ -208,4 +209,4 @@ class FileField extends FormField {
return true;
}
}
}

View File

@ -7,6 +7,8 @@ class FileTest extends SapphireTest {
static $fixture_file = 'FileTest.yml';
protected $extraDataObjects = array('FileTest_MyCustomFile');
function testCreateWithFilenameWithSubfolder() {
// Note: We can't use fixtures/setUp() for this, as we want to create the db record manually.
// Creating the folder is necessary to avoid having "Filename" overwritten by setName()/setRelativePath(),
@ -257,6 +259,43 @@ class FileTest extends SapphireTest {
$this->assertEquals($folder->Title, $newTitle3, "Folder Title updated after rename of Filename");
}
function testGetClassForFileExtension() {
$orig = File::$class_for_file_extension;
File::$class_for_file_extension['*'] = 'MyGenericFileClass';
File::$class_for_file_extension['foo'] = 'MyFooFileClass';
$this->assertEquals(
'MyFooFileClass',
File::get_class_for_file_extension('foo'),
'Finds directly mapped file classes'
);
$this->assertEquals(
'MyFooFileClass',
File::get_class_for_file_extension('FOO'),
'Works without case sensitivity'
);
$this->assertEquals(
'MyGenericFileClass',
File::get_class_for_file_extension('unknown'),
'Falls back to generic class for unknown extensions'
);
File::$class_for_file_extension = $orig;
}
function testFolderConstructChild() {
$orig = File::$class_for_file_extension;
File::$class_for_file_extension['gif'] = 'FileTest_MyCustomFile';
$folder1 = $this->objFromFixture('Folder', 'folder1');
$fileID = $folder1->constructChild('myfile.gif');
$file = DataObject::get_by_id('File', $fileID);
$this->assertEquals('FileTest_MyCustomFile', get_class($file));
File::$class_for_file_extension = $orig;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
function setUp() {
@ -308,4 +347,8 @@ class FileTest extends SapphireTest {
if (file_exists("../assets/FileTest-folder-renamed3")) Filesystem::removeFolder("../assets/FileTest-folder-renamed3");
}
}
class FileTest_MyCustomFile extends File implements TestOnly {
}