diff --git a/filesystem/File.php b/filesystem/File.php index 6998fbc13..efbf3aa00 100755 --- a/filesystem/File.php +++ b/filesystem/File.php @@ -15,19 +15,9 @@ * @subpackage filesystem */ class File extends DataObject { + static $default_sort = "Name"; - /** - * @var array Key is the extension, which has an array of MaxSize and WarnSize, - * e.g. array("jpg" => array("MaxSize"=>1000, "WarnSize=>500")) - */ - static $file_size_restrictions = array(); - - /** - * @var array Collection of extensions, e.g. array("jpg","gif") - */ - static $allowed_file_types = array(); - static $singular_name = "File"; static $plural_name = "Files"; @@ -39,16 +29,20 @@ class File extends DataObject { "Content" => "Text", "Sort" => "Int" ); + static $indexes = array( "SearchFields" => "fulltext (Filename,Title,Content)", ); + static $has_one = array( "Parent" => "File", "Owner" => "Member" ); + static $extensions = array( "Hierarchy", ); + static $belongs_many_many = array( "BackLinkTracking" => "SiteTree", ); @@ -60,32 +54,41 @@ class File extends DataObject { * @var array */ protected static $cache_file_fields = null; + + function Link($action = null) { + return Director::baseURL() . $this->RelativeLink($action); + } + function RelativeLink($action = null){ + return $this->Filename; + } + + function TreeTitle() { + return $this->Title; + } /** - * Set the maximum + * Event handler called before deleting from the database. + * You can overload this to clean up or otherwise process data before delete this + * record. Don't forget to call parent::onBeforeDelete(), though! */ - static function setMaxFileSize( $maxSize, $warningSize, $extension = '*' ) { - self::$file_size_restrictions[$extension]['MaxSize'] = $maxSize; - self::$file_size_restrictions[$extension]['WarnSize'] = $warningSize; - } - - static function getMaxFileSize($extension = '*') { - if(!isset(self::$file_size_restrictions[$extension])) { - if(isset(self::$file_size_restrictions['*'])) { - $extension = '*'; - } else { - return null; - } + protected function onBeforeDelete() { + parent::onBeforeDelete(); + + $this->autosetFilename(); + if($this->Filename && $this->Name && file_exists($this->getFullPath()) && !is_dir($this->getFullPath())) { + unlink($this->getFullPath()); } - return array( self::$file_size_restrictions[$extension]['MaxSize'], self::$file_size_restrictions[$extension]['WarnSize'] ); + if($brokenPages = $this->BackLinkTracking()) { + foreach($brokenPages as $brokenPage) { + Notifications::event("BrokenLink", $brokenPage, $brokenPage->OwnerID); + $brokenPage->HasBrokenFile = true; + $brokenPage->write(); + } + } } - - static function allowedFileType( $extension ) { - return true; - } - + /* * Find the given file */ @@ -138,19 +141,6 @@ class File extends DataObject { function Icon() { $ext = $this->Extension; if(!Director::fileExists("sapphire/images/app_icons/{$ext}_32.gif")) { - /*switch($ext) { - case "aif": case "au": case "mid": case "midi": case "mp3": case "ra": case "ram": case "rm": - case "mp3": case "wav": case "m4a": - $ext = "audio"; break; - - case "arc": case "rar": case "tar": case "gz": case "tgz": case "bz2": case "dmg": - $ext = "zip"; break; - - case "bmp": case "gif": case "jpg": case "jpeg": case "pcx": case "tif": case "png": - $ext = "image"; break; - - }*/ - $ext = $this->appCategory(); } @@ -162,81 +152,20 @@ class File extends DataObject { } /** - * Save an file passed from a form post into this object + * Save an file passed from a form post into this object. + * DEPRECATED Please instanciate an Upload-object instead and pass the file + * via {Upload->loadIntoFile()}. + * + * @param $tmpFile array Indexed array that PHP generated for every file it uploads. + * @return Boolean|string Either success or error-message. */ - function loadUploaded($tmpFile, $folderName = 'Uploads') { - if(!is_array($tmpFile)) user_error("File::loadUploaded() Not passed an array. Most likely, the form hasn't got the right enctype", E_USER_ERROR); - if(!$tmpFile['size']) return; + function loadUploaded($tmpFile) { + user_error('File::loadUploaded is deprecated, please use the Upload class directly.', E_USER_NOTICE); + $upload = new Upload(); + $upload->loadIntoFile($tmpFile, $this); - // @TODO This puts a HUGE limitation on files especially when lots - // have been uploaded. - $base = dirname(dirname($_SERVER['SCRIPT_FILENAME'])); - $class = $this->class; - $parentFolder = Folder::findOrMake($folderName); - - // Create a folder for uploading. - if(!file_exists("$base/assets")){ - mkdir("$base/assets", Filesystem::$folder_create_mask); - } - if(!file_exists("$base/assets/$folderName")){ - mkdir("$base/assets/$folderName", Filesystem::$folder_create_mask); - } - - // Generate default filename - $file = str_replace(' ', '-',$tmpFile['name']); - $file = ereg_replace('[^A-Za-z0-9+.-]+','',$file); - $file = ereg_replace('-+', '-',$file); - $file = basename($file); - - $file = "assets/$folderName/$file"; - - while(file_exists("$base/$file")) { - $i = isset($i) ? ($i+1) : 2; - $oldFile = $file; - if(substr($file, strlen($file) - strlen('.tar.gz')) == '.tar.gz' || - substr($file, strlen($file) - strlen('.tar.bz2')) == '.tar.bz2') { - $file = ereg_replace('[0-9]*(\.tar\.[^.]+$)',$i . '\\1', $file); - } else { - $file = ereg_replace('[0-9]*(\.[^.]+$)',$i . '\\1', $file); - } - if($oldFile == $file && $i > 2) user_error("Couldn't fix $file with $i", E_USER_ERROR); - } - - if(file_exists($tmpFile['tmp_name']) && copy($tmpFile['tmp_name'], "$base/$file")) { - // Update with the new image - /*$this->Filename = */ // $this->Name = null; - // $this->Filename = $file; - - // This is to prevent it from trying to rename the file - $this->record['Name'] = null; - $this->ParentID = $parentFolder->ID; - $this->Name = basename($file); - $this->write(); - return true; - } else { - user_error("File::loadUploaded: Couldn't copy '$tmpFile[tmp_name]' to '$file'", E_USER_ERROR); - return false; - } - } - - /** - * This function ensures the file table is correct with the files in the assets folder. - */ - static function sync() { - singleton('Folder')->syncChildren(); - $finished = false; - while(!$finished) { - $orphans = DB::query("SELECT C.ID FROM File AS C LEFT JOIN File AS P ON C.ParentID = P.ID WHERE P.ID IS NULL AND C.ParentID > 0"); - $finished = true; - if($orphans) foreach($orphans as $orphan) { - $finished = false; - // Delete the database record but leave the filesystem alone - $file = DataObject::get_by_id("File", $orphan['ID']); - $file->deleteDatabaseOnly(); - } - } - + return $upload->isError(); } /* @@ -246,79 +175,6 @@ class File extends DataObject { Debug::show(get_defined_functions()); } - function loadallcontent() { - ini_set("max_execution_time", 50000); - $allFiles = DataObject::get("File"); - $total = $allFiles->TotalItems(); - - $i = 0; - foreach($allFiles as $file) { - $i++; - $tmp = TEMP_FOLDER; - `echo "$i / $total" > $tmp/progress`; - $file->write(); - } - } - - /** - * Gets the content of this file and puts it in the field Content - */ - function loadContent() { - $filename = escapeshellarg($this->getFullPath()); - switch(strtolower($this->getExtension())){ - case 'pdf': - $content = `pdftotext $filename -`; - - //echo("
Content for $this->Filename:\n$content"); - $this->Content = $content; - break; - case 'doc': - $content = `catdoc $filename`; - $this->Content = $content; - break; - case 'ppt': - $content = `catppt $filename`; - $this->Content = $content; - break; - case 'txt'; - $content = file_get_contents($this->FileName); - $this->Content = $content; - } - } - - function Link($action = null) { - return Director::baseURL() . $this->RelativeLink($action); - } - - function RelativeLink($action = null){ - return $this->Filename; - } - - function TreeTitle() { - if($this->hasMethod('alternateTreeTitle')) return $this->alternateTreeTitle(); - else return $this->Title; - } - - /** - * Event handler called before deleting from the database. - * You can overload this to clean up or otherwise process data before delete this - * record. Don't forget to call parent::onBeforeDelete(), though! - */ - protected function onBeforeDelete() { - parent::onBeforeDelete(); - - $this->autosetFilename(); - if($this->Filename && $this->Name && file_exists($this->getFullPath()) && !is_dir($this->getFullPath())) unlink($this->getFullPath()); - - if($brokenPages = $this->BackLinkTracking()) { - foreach($brokenPages as $brokenPage) { - Notifications::event("BrokenLink", $brokenPage, $brokenPage->OwnerID); - $brokenPage->HasBrokenFile = true; - $brokenPage->write(); - } - } - } - /** * Delete the database record (recursively for folders) without touching the filesystem */ @@ -343,8 +199,6 @@ class File extends DataObject { $brokenPage->write(); } } - - $this->loadContent(); } /** @@ -549,6 +403,7 @@ class File extends DataObject { function getExtension() { return strtolower(substr($this->getField('Filename'),strrpos($this->getField('Filename'),'.')+1)); } + function getFileType() { $types = array( 'gif' => 'GIF Image - good for diagrams', @@ -563,6 +418,7 @@ class File extends DataObject { 'pdf' => 'Adobe Acrobat PDF file', ); $ext = $this->getExtension(); + return isset($types[$ext]) ? $types[$ext] : 'unknown'; } @@ -571,13 +427,16 @@ class File extends DataObject { */ function getSize() { $size = $this->getAbsoluteSize(); - if($size){ - if($size < 1024) return $size . ' bytes'; - if($size < 1024*10) return (round($size/1024*10)/10). ' KB'; - if($size < 1024*1024) return round($size/1024) . ' KB'; - if($size < 1024*1024*10) return (round(($size/1024)/1024*10)/10) . ' MB'; - if($size < 1024*1024*1024) return round(($size/1024)/1024) . ' MB'; - } + + return ($size) ? self::format_size($size) : false; + } + + public static function format_size($size) { + if($size < 1024) return $size . ' bytes'; + if($size < 1024*10) return (round($size/1024*10)/10). ' KB'; + if($size < 1024*1024) return round($size/1024) . ' KB'; + if($size < 1024*1024*10) return (round(($size/1024)/1024*10)/10) . ' MB'; + if($size < 1024*1024*1024) return round(($size/1024)/1024) . ' MB'; } /** @@ -592,33 +451,6 @@ class File extends DataObject { } } - //--------------------------------------------------------------------------------------------------// - // Helper control functions - function moverootfilesto() { - if($folder = $this->urlParams[ID]) { - $newParent = Folder::findOrMake($folder); - $files = DataObject::get("File", "ClassName != 'Folder' AND ParentID = 0"); - foreach($files as $file) { - echo "
Done!"; - } - /** * Select clause for DataObject::get('File') operations/ @@ -676,7 +508,74 @@ class File extends DataObject { self::$cache_file_fields = null; } + + + + + + + + + + + /** + * DEPRECATED + */ + static $file_size_restrictions = array(); + + /** + * DEPRECATED + */ + static $allowed_file_types = array(); + + /** + * DEPRECATED + */ + static function setMaxFileSize( $maxSize, $warningSize, $extension = '*' ) { + user_error('File::setMaxFileSize is deprecated',E_USER_ERROR); + } + + /** + * DEPRECATED + */ + static function getMaxFileSize($extension = '*') { + user_error('File::getMaxFileSize is deprecated',E_USER_ERROR); + } + + /** + * DEPRECATED + */ + static function sync() { + user_error('File::sync is deprecated - please use FileSystem::sync',E_USER_ERROR); + } + + /** + * DEPRECATED + */ + function moverootfilesto() { + user_error('File::moverootfilesto is deprecated - please use FileSystem::moverootfilesto',E_USER_ERROR); + } + + /** + * DEPRECATED + */ + function fixfiles() { + user_error('File::fixfiles is deprecated - please use FileSystem::fixfiles',E_USER_ERROR); + } + +/** + * DEPRECATED + */ + function loadContent() { + user_error('File::loadContent deprecated',E_USER_ERROR); + } + + /** + * DEPRECATED + */ + public function loadallcontent() { + user_error('File::loadallcontent deprecated',E_USER_ERROR); + } } - ?> diff --git a/filesystem/Filesystem.php b/filesystem/Filesystem.php index b875dc7f9..4263489dd 100755 --- a/filesystem/Filesystem.php +++ b/filesystem/Filesystem.php @@ -1,12 +1,7 @@ urlParams['ID']) { + $newParent = Folder::findOrMake($folder); + $files = DataObject::get("File", "ClassName != 'Folder' AND ParentID = 0"); + foreach($files as $file) { + echo "
Done!";
+ }
+
/*
* Return the most recent modification time of anything in the folder.
* @param $folder The folder, relative to the site root
* @param $extensionList An option array of file extensions to limit the search to
*/
-
- protected static $cache_folderModTime;
static function folderModTime($folder, $extensionList = null, $recursiveCall = false) {
//$cacheID = $folder . ',' . implode(',', $extensionList);
//if(!$recursiveCall && self::$cache_folderModTime[$cacheID]) return self::$cache_folderModTime[$cacheID];
@@ -89,7 +113,25 @@ class Filesystem extends Object {
else return $filename[0] == '/';
}
+ /**
+ * This function ensures the file table is correct with the files in the assets folder.
+ */
+ static function sync() {
+ singleton('Folder')->syncChildren();
+ $finished = false;
+ while(!$finished) {
+ $orphans = DB::query("SELECT C.ID FROM File AS C LEFT JOIN File AS P ON C.ParentID = P.ID WHERE P.ID IS NULL AND C.ParentID > 0");
+ $finished = true;
+ if($orphans) foreach($orphans as $orphan) {
+ $finished = false;
+ // Delete the database record but leave the filesystem alone
+ $file = DataObject::get_by_id("File", $orphan['ID']);
+ $file->deleteDatabaseOnly();
+ }
+ }
+ }
+
}
?>
\ No newline at end of file
diff --git a/filesystem/Upload.php b/filesystem/Upload.php
new file mode 100644
index 000000000..b45643a0f
--- /dev/null
+++ b/filesystem/Upload.php
@@ -0,0 +1,325 @@
+
+ * array("jpg","GIF")
+ *
+ *
+ * @var array
+ */
+ public $allowedExtensions = array();
+
+ /**
+ * Processing errors that can be evaluated,
+ * e.g. by Form-validation.
+ *
+ * @var array
+ */
+ protected $errors = array();
+
+ /**
+ * A foldername relative to /assets,
+ * where all uploaded files are stored by default.
+ *
+ * @var string
+ */
+ public static $uploads_folder = "Uploads";
+
+ /**
+ * Save an file passed from a form post into this object.
+ *
+ * @param $tmpFile array Indexed array that PHP generated for every file it uploads.
+ * @param $folderPath string Folder path relative to /assets
+ * @return Boolean|string Either success or error-message.
+ */
+ function load($tmpFile, $folderPath = false) {
+ $this->clearErrors();
+
+ if(!$folderPath) $folderPath = self::$uploads_folder;
+
+ if(!$this->file) $this->file = new File();
+
+ if(!is_array($tmpFile)) {
+ user_error("File::loadUploaded() Not passed an array. Most likely, the form hasn't got the right enctype", E_USER_ERROR);
+ }
+
+ if(!$tmpFile['size']) {
+ $this->errors[] = _t('File.NOFILESIZE', 'Filesize is zero bytes.');
+ return false;
+ }
+
+ $valid = $this->validate($tmpFile);
+ if(!$valid) return false;
+
+ // @TODO This puts a HUGE limitation on files especially when lots
+ // have been uploaded.
+ $base = Director::baseFolder();
+ $parentFolder = Folder::findOrMake($folderPath);
+
+ // Create a folder for uploading.
+ if(!file_exists("$base/assets")){
+ mkdir("$base/assets", Filesystem::$folder_create_mask);
+ }
+ if(!file_exists("$base/assets/" . $folderPath)){
+ mkdir("$base/assets/" . $folderPath, Filesystem::$folder_create_mask);
+ }
+
+ // Generate default filename
+ $fileName = str_replace(' ', '-',$tmpFile['name']);
+ $fileName = ereg_replace('[^A-Za-z0-9+.-]+','',$fileName);
+ $fileName = ereg_replace('-+', '-',$fileName);
+ $fileName = basename($fileName);
+
+ $relativeFilePath = "assets/" . $folderPath . "/$fileName";
+
+ // if filename already exists, version the filename (e.g. test.gif to test1.gif)
+ while(file_exists("$base/$relativeFilePath")) {
+ $i = isset($i) ? ($i+1) : 2;
+ $oldFilePath = $relativeFilePath;
+ // make sure archives retain valid extensions
+ if(substr($relativeFilePath, strlen($relativeFilePath) - strlen('.tar.gz')) == '.tar.gz' ||
+ substr($relativeFilePath, strlen($relativeFilePath) - strlen('.tar.bz2')) == '.tar.bz2') {
+ $relativeFilePath = ereg_replace('[0-9]*(\.tar\.[^.]+$)',$i . '\\1', $relativeFilePath);
+ } else {
+ $relativeFilePath = ereg_replace('[0-9]*(\.[^.]+$)',$i . '\\1', $relativeFilePath);
+ }
+ if($oldFilePath == $relativeFilePath && $i > 2) user_error("Couldn't fix $relativeFilePath with $i tries", E_USER_ERROR);
+ }
+
+ if(file_exists($tmpFile['tmp_name']) && copy($tmpFile['tmp_name'], "$base/$relativeFilePath")) {
+ $this->file->ParentID = $parentFolder->ID;
+ // This is to prevent it from trying to rename the file
+ $this->file->Name = basename($relativeFilePath);
+ $this->file->write();
+ return true;
+ } else {
+ $this->errors[] = _t('File.NOFILESIZE', 'Filesize is zero bytes.');
+ return false;
+ }
+ }
+
+ /**
+ * Load temporary PHP-upload into File-object.
+ *
+ * @param array $tmpFile
+ * @param File $file
+ * @return Boolean
+ */
+ public function loadIntoFile($tmpFile, $file) {
+ $this->file = $file;
+
+ return $this->load($tmpFile);
+ }
+
+ /**
+ * Container for all validation on the file
+ * (e.g. size and extension restrictions).
+ * Is NOT connected to the {Validator} classes,
+ * please have a look at {FileField->validate()}
+ * for an example implementation of external validation.
+ *
+ * @param array $tmpFile
+ * @return boolean
+ */
+ public function validate($tmpFile) {
+ $pathInfo = pathinfo($tmpFile['name']);
+ // filesize validation
+ if(!$this->isValidSize($tmpFile)) {
+ $this->errors[] = sprintf(
+ _t(
+ 'File.TOOLARGE',
+ 'Filesize is too large, maximum %s allowed.',
+ PR_MEDIUM,
+ 'Argument 1: Filesize (e.g. 1MB)'
+ ),
+ File::format_size($this->getAllowedMaxFileSize($pathInfo['extension']))
+ );
+ return false;
+ }
+
+ // extension validation
+ if(!$this->isValidExtension($tmpFile)) {
+ $this->errors[] = sprintf(
+ _t(
+ 'File.INVALIDEXTENSION',
+ 'Extension is not allowed (valid: %s)',
+ PR_MEDIUM,
+ 'Argument 1: Comma-separated list of valid extensions'
+ ),
+ implode(',',$this->allowedExtensions)
+ );
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get file-object, either generated from {load()},
+ * or manually set.
+ *
+ * @return File
+ */
+ public function getFile() {
+ return $this->file;
+ }
+
+ /**
+ * Set a file-object (similiar to {loadIntoFile()})
+ *
+ * @param File $file
+ */
+ public function setFile($file) {
+ $this->file = $file;
+ }
+
+ /**
+ * Get maximum file size for all or specified file extension.
+ *
+ * @param string $ext
+ * @return int Filesize in bytes
+ */
+ public function getAllowedMaxFileSize($ext = null) {
+ $ext = strtolower($ext);
+ if(isset($ext) && isset($this->allowedMaxFileSize[$ext])) {
+ return $this->allowedMaxFileSize[$ext];
+ } else {
+ return (isset($this->allowedMaxFileSize['*'])) ? $this->allowedMaxFileSize['*'] : false;
+ }
+ }
+
+ /**
+ * Set filesize maximums (in bytes).
+ * Automatically converts extensions to lowercase
+ * for easier matching.
+ *
+ * Example:
+ *
+ * array('*' => 200, 'jpg' => 1000)
+ *
+ *
+ * @param array|int $rules
+ */
+ public function setAllowedMaxFileSize($rules) {
+ if(is_array($rules) && count($rules)) {
+ // make sure all extensions are lowercase
+ $rules = array_change_key_case($rules, CASE_LOWER);
+ $this->allowedMaxFileSize = $rules;
+ } elseif((int)$rules > 0) {
+ $this->allowedMaxFileSize['*'] = (int)$rules;
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedExtensions() {
+ return $this->allowedExtensions;
+ }
+
+ /**
+ * @param array $rules
+ */
+ public function setAllowedExtensions($rules) {
+ if(!is_array($rules)) return false;
+
+ // make sure all rules are lowercase
+ foreach($rules as &$rule) $rule = strtolower($rule);
+
+ $this->allowedExtensions = $rules;
+ }
+
+ /**
+ * Determines if the bytesize of an uploaded
+ * file is valid - can be defined on an
+ * extension-by-extension basis in {$allowedMaxFileSize}
+ *
+ * @param array $tmpFile
+ * @return boolean
+ */
+ public function isValidSize($tmpFile) {
+ $pathInfo = pathinfo($tmpFile['name']);
+ $maxSize = $this->getAllowedMaxFileSize(strtolower($pathInfo['extension']));
+ return (!$tmpFile['size'] || !$maxSize || (int)$tmpFile['size'] < $maxSize);
+ }
+
+ /**
+ * Determines if the temporary file has a valid extension
+ *
+ * @param array $tmpFile
+ * @return boolean
+ */
+ public function isValidExtension($tmpFile) {
+ $pathInfo = pathinfo($tmpFile['name']);
+ return (!count($this->allowedExtensions) || array_key_exists(strtolower($pathInfo['extension']), $this->allowedExtensions));
+ }
+
+
+ /**
+ * Clear out all errors (mostly set by {loadUploaded()})
+ */
+ public function clearErrors() {
+ $this->errors = array();
+ }
+
+ /**
+ * Determines wether previous operations caused an error.
+ *
+ * @return boolean
+ */
+ public function isError() {
+ return (count($this->errors));
+ }
+
+ /**
+ * Return all errors that occurred while processing so far
+ * (mostly set by {loadUploaded()})
+ *
+ * @return array
+ */
+ public function getErrors() {
+ return $this->errors;
+ }
+
+}
+?>
\ No newline at end of file
diff --git a/forms/EditableFileField.php b/forms/EditableFileField.php
index 2af539893..8858bf3ca 100755
--- a/forms/EditableFileField.php
+++ b/forms/EditableFileField.php
@@ -1,13 +1,7 @@
"File"
);
- // TODO Interface and properties for these properties
- static $file_size_restrictions = array();
- static $allowed_file_types = array();
+ /**
+ * @see {Upload->allowedMaxFileSize}
+ * @var int
+ */
+ public static $allowed_max_file_size;
+
+ /**
+ * @see {Upload->allowedExtensions}
+ * @var array
+ */
+ public static $allowed_extensions = array();
static $singular_name = 'File field';
static $plural_names = 'File fields';
- function ExtraOptions() {
- return parent::ExtraOptions();
- }
-
function getFormField() {
- if( $field = parent::getFormField() )
+ if($field = parent::getFormField())
return $field;
return new FileField($this->Name, $this->Title, $this->getField('Default'));
// TODO We can't use the preview feature because FileIFrameField also shows the "From the file store" functionality
@@ -41,8 +39,8 @@ class EditableFileField extends EditableFormField {
return new FileField($this->Name, $this->Title, $this->getField('Default'));
}
- function createSubmittedField($data, $submittedForm, $fieldClass = "SubmittedFileField" ) {
- if( !$_FILES[$this->Name] )
+ function createSubmittedField($data, $submittedForm, $fieldClass = "SubmittedFileField") {
+ if(!$_FILES[$this->Name])
return null;
$submittedField = new $fieldClass();
@@ -51,12 +49,17 @@ class EditableFileField extends EditableFormField {
$submittedField->ParentID = $submittedForm->ID;
// create the file from post data
- $uploadedFile = new File();
- $uploadedFile->set_stat('file_size_restrictions',$this->stat('file_size_restrictions'));
- $uploadedFile->set_stat('allowed_file_types',$this->stat('allowed_file_types'));
- $uploadedFile->loadUploaded( $_FILES[$this->Name] );
+ $upload = new Upload();
+ $upload->setAllowedExtensions(self::$allowed_extensions);
+ $upload->setAllowedMaxFileSize(self::$allowed_max_file_size);
+
+ // upload file
+ $upload->load($_FILES[$this->Name]);
+
+ $uploadedFile = $upload->getFile();
$submittedField->UploadedFileID = $uploadedFile->ID;
$submittedField->write();
+
return $submittedField;
}
}
diff --git a/forms/FileField.php b/forms/FileField.php
index ce5de7f9e..391dc26f5 100755
--- a/forms/FileField.php
+++ b/forms/FileField.php
@@ -1,18 +1,71 @@
+ * array("jpg","GIF")
+ *
+ *
+ * @var array
+ */
+ public $allowedExtensions = array();
+
+ /**
+ * Flag to automatically determine and save a has_one-relationship
+ * on the saved record (e.g. a "Player" has_one "PlayerImage" would
+ * trigger saving the ID of newly created file into "PlayerImageID"
+ * on the record).
+ *
+ * @var unknown_type
+ */
+ public $relationAutoSetting = true;
+
+ /**
+ * Upload object (needed for validation
+ * and actually moving the temporary file
+ * created by PHP).
+ *
+ * @var Upload
+ */
+ protected $upload;
+
+ /**
+ * Partial filesystem path relative to /assets directory.
+ * Defaults to 'Uploads'.
+ *
+ * @var string
+ */
+ protected $folderName = 'Uploads';
+
/**
* Create a new file field.
+ *
* @param string $name The internal field name, passed to forms.
* @param string $title The field label.
* @param int $value The value of the field.
@@ -20,33 +73,145 @@ class FileField extends FormField {
* @param string $rightTitle Used in SmallFieldHolder() to force a right-aligned label
* @param string $folderName Folder to upload files to
*/
- function __construct($name, $title = null, $value = null, $form = null, $rightTitle = null, $folderName = 'Uploads') {
- $this->folderName = $folderName;
+ function __construct($name, $title = null, $value = null, $form = null, $rightTitle = null, $folderName = null) {
+ if(isset($folderName)) $this->folderName = $folderName;
+ $this->upload = new Upload();
parent::__construct($name, $title, $value, $form, $rightTitle);
}
public function Field() {
return
- $this->createTag("input", array("type" => "file", "name" => $this->name, "id" => $this->id())) .
- $this->createTag("input", array("type" => "hidden", "name" => "MAX_FILE_SIZE", "value" => 30*1024*1024));
+ $this->createTag("input",
+ array(
+ "type" => "file",
+ "name" => $this->name,
+ "id" => $this->id(),
+ "tabindex" => $this->getTabIndex()
+ )
+ ) .
+ $this->createTag("input",
+ array(
+ "type" => "hidden",
+ "name" => "MAX_FILE_SIZE",
+ "value" => $this->getAllowedMaxFileSize(),
+ "tabindex" => $this->getTabIndex()
+ )
+ );
}
public function saveInto(DataObject $record) {
- $fieldName = $this->name . 'ID';
- $hasOnes = $record->has_one($this->name);
+ if(!isset($_FILES[$this->name])) return false;
- // assume that the file is connected via a has-one
- if(!$hasOnes || !isset($_FILES[$this->name]) || !$_FILES[$this->name]['name']) return false;
+ $this->upload->setAllowedExtensions($this->allowedExtensions);
+ $this->upload->setAllowedMaxFileSize($this->allowedMaxFileSize);
+ $this->upload->load($_FILES[$this->name]);
+ if($this->upload->isError()) return false;
- $file = new File();
- $file->loadUploaded($_FILES[$this->name], $this->folderName);
+ $file = $this->upload->getFile();
- $record->$fieldName = $file->ID;
+ if($this->relationAutoSetting) {
+ $fieldName = $this->name . 'ID';
+ // assume that the file is connected via a has-one
+ $hasOnes = $record->has_one($this->name);
+ if(!$hasOnes) return false;
+
+ // save to record
+ $record->$fieldName = $file->ID;
+ }
}
public function Value() {
return $_FILES[$this->Name()];
}
+
+ /**
+ * Get maximum file size for all or specified file extension.
+ * Falls back to the default filesize restriction ('*')
+ * if the extension was not found.
+ *
+ * @param string $ext
+ * @return int Filesize in bytes (0 means no filesize set)
+ */
+ public function getAllowedMaxFileSize($ext = null) {
+ $ext = strtolower($ext);
+ if(isset($ext) && isset($this->allowedMaxFileSize[$ext])) {
+ return $this->allowedMaxFileSize[$ext];
+ } else {
+ return (isset($this->allowedMaxFileSize['*'])) ? $this->allowedMaxFileSize['*'] : 0;
+ }
+ }
+
+ /**
+ * Set filesize maximums (in bytes).
+ * Automatically converts extensions to lowercase
+ * for easier matching.
+ *
+ * Example:
+ *
+ * array('*' => 200, 'jpg' => 1000)
+ *
+ *
+ * @param unknown_type $rules
+ */
+ public function setAllowedMaxFileSize($rules) {
+ if(is_array($rules)) {
+ // make sure all extensions are lowercase
+ $rules = array_change_key_case($rules, CASE_LOWER);
+ $this->allowedMaxFileSize = $rules;
+ } else {
+ $this->allowedMaxFileSize['*'] = (int)$rules;
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedExtensions() {
+ return $this->allowedExtensions;
+ }
+
+ /**
+ * @param array $rules
+ */
+ public function setAllowedExtensions($rules) {
+ if(!is_array($rules)) return false;
+
+ // make sure all rules are lowercase
+ foreach($rules as &$rule) $rule = strtolower($rule);
+
+ $this->allowedExtensions = $rules;
+ }
+
+ /**
+ * @param string $folderName
+ */
+ public function setFolderName($folderName) {
+ $this->folderName = $folderName;
+ }
+
+ /**
+ * @return string
+ */
+ public function getFolderName() {
+ return $folderName;
+ }
+
+ public function validate($validator) {
+ $tmpFile = $_FILES[$this->name];
+ $this->upload->setAllowedExtensions($this->allowedExtensions);
+ $this->upload->setAllowedMaxFileSize($this->allowedMaxFileSize);
+
+ $valid = $this->upload->validate($tmpFile);
+ if(!$valid) {
+ $errors = $this->upload->getErrors();
+ if($errors) foreach($errors as $error) {
+ $validator->validationError($this->name, $error, "validation", false);
+ }
+ return false;
+ }
+
+ return true;
+ }
}
?>
diff --git a/tests/filesystem/UploadTest.php b/tests/filesystem/UploadTest.php
new file mode 100644
index 000000000..9297defe7
--- /dev/null
+++ b/tests/filesystem/UploadTest.php
@@ -0,0 +1,63 @@
+ $tmpFileName,
+ 'type' => 'text/plaintext',
+ 'size' => filesize($tmpFilePath),
+ 'tmp_name' => $tmpFilePath,
+ 'extension' => 'txt',
+ 'error' => UPLOAD_ERR_OK,
+ );
+
+ // test upload into default folder
+ $u1 = new Upload();
+ $u1->load($tmpFile);
+ $file1 = $u1->getFile();
+ $this->assertTrue(
+ file_exists($file1->getFullPath()),
+ 'File upload to standard directory in /assets'
+ );
+ $this->assertTrue(
+ (strpos($file1->getFullPath(),Director::baseFolder() . '/assets/' . Upload::$uploads_folder) !== false),
+ 'File upload to standard directory in /assets'
+ );
+ $file1->delete();
+
+ // test upload into custom folder
+ $customFolder = 'UploadTest_testUpload';
+ $u2 = new Upload();
+ $u2->load($tmpFile, $customFolder);
+ $file2 = $u2->getFile();
+ $this->assertTrue(
+ file_exists($file2->getFullPath()),
+ 'File upload to custom directory in /assets'
+ );
+ $this->assertTrue(
+ (strpos($file2->getFullPath(),Director::baseFolder() . '/assets/' . $customFolder) !== false),
+ 'File upload to custom directory in /assets'
+ );
+ $file2->delete();
+
+ unlink($tmpFilePath);
+ }
+
+ function testAllowedFilesize() {
+ // @todo
+ }
+
+ function testAllowedExtensions() {
+ // @todo
+ }
+
+}
+?>
\ No newline at end of file