From afeccbc9cbc4c2a95a97ee38b30be92d4fdd8f69 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Mon, 22 Aug 2011 13:04:15 +0200 Subject: [PATCH] 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. --- filesystem/File.php | 51 +++++++++++++++++++++++++++++++++-- filesystem/Folder.php | 7 +---- filesystem/Upload.php | 5 +++- forms/FileField.php | 7 ++--- tests/filesystem/FileTest.php | 43 +++++++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 12 deletions(-) diff --git a/filesystem/File.php b/filesystem/File.php index e0d57d4fd..fb4dd4f34 100644 --- a/filesystem/File.php +++ b/filesystem/File.php @@ -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; + } + } + +} diff --git a/filesystem/Folder.php b/filesystem/Folder.php index e61d774de..5b50c99d1 100644 --- a/filesystem/Folder.php +++ b/filesystem/Folder.php @@ -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; diff --git a/filesystem/Upload.php b/filesystem/Upload.php index e1dbfaab9..137e7079d 100644 --- a/filesystem/Upload.php +++ b/filesystem/Upload.php @@ -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); diff --git a/forms/FileField.php b/forms/FileField.php index bcae2dd7f..a6e75af9f 100644 --- a/forms/FileField.php +++ b/forms/FileField.php @@ -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; } -} \ No newline at end of file +} diff --git a/tests/filesystem/FileTest.php b/tests/filesystem/FileTest.php index 9a77e0ce2..b09483951 100644 --- a/tests/filesystem/FileTest.php +++ b/tests/filesystem/FileTest.php @@ -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 { + } \ No newline at end of file