From d25a05d74883c56af8ceff1232e20de365eac9c5 Mon Sep 17 00:00:00 2001 From: snipe Date: Wed, 26 Aug 2020 11:53:36 -0700 Subject: [PATCH 1/9] Misc Flysystem/image upload request fixes --- .../Controllers/AssetModelsController.php | 2 +- app/Http/Controllers/SettingsController.php | 22 +----- .../Controllers/Users/UsersController.php | 4 +- app/Http/Requests/ImageUploadRequest.php | 78 ++++++++----------- .../forms/edit/image-upload.blade.php | 2 +- 5 files changed, 41 insertions(+), 67 deletions(-) diff --git a/app/Http/Controllers/AssetModelsController.php b/app/Http/Controllers/AssetModelsController.php index 6673624fc3..4a4478464f 100755 --- a/app/Http/Controllers/AssetModelsController.php +++ b/app/Http/Controllers/AssetModelsController.php @@ -85,7 +85,7 @@ class AssetModelsController extends Controller $model->fieldset_id = e($request->input('custom_fieldset')); } - $model = $request->handleImages($model,600,null,null, public_path().'/uploads/models'); + $model = $request->handleImages($model,600,'image', public_path().'/uploads/models', 'image'); // Was it created? if ($model->save()) { diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index 53dae7c849..320f12dfca 100755 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -410,27 +410,9 @@ class SettingsController extends Controller } - if ($request->hasFile('logo')) { - $image = $request->file('logo'); - $ext = $image->getClientOriginalExtension(); - $setting->logo = $file_name = 'logo-'.date('U').'.'. $ext; + $setting = $request->handleImages('logo',600,'logo','', 'logo'); - if ('svg' != $image->getClientOriginalExtension()) { - $upload = Image::make($image->getRealPath())->resize(null, 150, function ($constraint) { - $constraint->aspectRatio(); - $constraint->upsize(); - }); - } - - // This requires a string instead of an object, so we use ($string) - Storage::disk('public')->put($file_name, (string) $upload->encode()); - - // Remove Current image if exists - if (($setting->logo) && (file_exists($file_name))) { - Storage::disk('public')->delete($file_name); - } - - } elseif ('1' == $request->input('clear_logo')) { + if ('1' == $request->input('clear_logo')) { Storage::disk('public')->delete($setting->logo); $setting->logo = null; $setting->brand = 1; diff --git a/app/Http/Controllers/Users/UsersController.php b/app/Http/Controllers/Users/UsersController.php index 1d06b1dc03..bfba9b26f7 100755 --- a/app/Http/Controllers/Users/UsersController.php +++ b/app/Http/Controllers/Users/UsersController.php @@ -291,9 +291,11 @@ class UsersController extends Controller $user->permissions = json_encode($permissions_array); - app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar'); + // Handle uploaded avatar + app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'avatar', 'avatars', 'avatar'); + //\Log::debug(print_r($user, true)); // Was the user updated? if ($user->save()) { diff --git a/app/Http/Requests/ImageUploadRequest.php b/app/Http/Requests/ImageUploadRequest.php index 72ec9021a7..39b61be45a 100644 --- a/app/Http/Requests/ImageUploadRequest.php +++ b/app/Http/Requests/ImageUploadRequest.php @@ -43,49 +43,48 @@ class ImageUploadRequest extends Request * @param String $path location for uploaded images, defaults to uploads/plural of item type. * @return SnipeModel Target asset is being checked out to. */ - public function handleImages($item, $w = 600, $fieldname = 'image', $path = null, $db_fieldname = 'image') + public function handleImages($item, $w = 600, $form_fieldname = null, $path = null, $db_fieldname = 'image') { - \Log::debug('Handle file upload'); - $type = strtolower(class_basename(get_class($item))); if (is_null($path)) { - $path = str_plural($type); + $path = str_plural($type); - if ($type=='assetmodel') { - \Log::debug('This is an asset model! Override the path'); + if ($type == 'assetmodel') { $path = 'models'; } - if ($type=='user') { - \Log::debug('This is a user! Override the path'); + if ($type == 'user') { $path = 'avatars'; } } - \Log::info('Path is: '.$path); + if (is_null($form_fieldname)) { + $form_fieldname = 'image'; + } + + // This is dumb, but we need it for overriding field names for exceptions like avatars and logo uploads + if (is_null($db_fieldname)) { + $use_db_field = $form_fieldname; + } else { + $use_db_field = $db_fieldname; + } + + + \Log::info('Image path is: '.$path); \Log::debug('Type is: '.$type); - \Log::debug('Image path is: '.$path); - \Log::debug('Image fieldname is: '.$fieldname); + \Log::debug('Form fieldname is: '.$form_fieldname); + \Log::debug('DB fieldname is: '.$use_db_field); \Log::debug('Trying to upload to '. $path); - if ($this->hasFile($fieldname)) { + \Log::debug($this->file()); + + if ($this->hasFile($form_fieldname)) { if (!config('app.lock_passwords')) { - if (!Storage::disk('public')->exists($path)) - { - \Log::debug($path); - // Storage::disk('public')->makeDirectory($path, 775); - } - - if (!is_dir($path)) { - \Log::info($path.' does not exist'); - //mkdir($path); - } - - $image = $this->file($fieldname); + $image = $this->file($form_fieldname); $ext = $image->getClientOriginalExtension(); $file_name = $type.'-'.str_random(18).'.'.$ext; @@ -120,40 +119,32 @@ class ImageUploadRequest extends Request // Remove Current image if exists - if (($item->{$fieldname}) && (Storage::disk('public')->exists($path.'/'.$item->{$fieldname}))) { - \Log::debug('A file already exists that we are replacing - we should delete the old one.'); + if (Storage::disk('public')->exists($path.'/'.$item->{$use_db_field})) { - // Assign the new filename as the fieldname - if (is_null($db_fieldname)) { - $item->{$fieldname} = $file_name; - } else { - $item->{$db_fieldname} = $file_name; - } + \Log::debug('A file already exists that we are replacing - we should delete the old one.'); try { - Storage::disk('public')->delete($path.'/'.$item->{$fieldname}); + Storage::disk('public')->delete($path.'/'.$item->{$use_db_field}); + \Log::debug('Old file '.$path.'/'.$file_name.' has been deleted.'); } catch (\Exception $e) { - \Log::debug('Could not delete old file. '.$path.'/'.$item->{$fieldname}.' does not exist?'); + \Log::debug('Could not delete old file. '.$path.'/'.$file_name.' does not exist?'); } } + $item->{$use_db_field} = $file_name; + } // If the user isn't uploading anything new but wants to delete their old image, do so } else { - + \Log::debug('No file passed for '.$form_fieldname); if ($this->input('image_delete')=='1') { + \Log::debug('Deleting image'); try { - - if (is_null($db_fieldname)) { - $item->{$fieldname} = null; - Storage::disk('public')->delete($path . '/' . $item->{$fieldname}); - } else { - $item->{$db_fieldname} = null; - Storage::disk('public')->delete($path . '/' . $item->{$fieldname}); - } + Storage::disk('public')->delete($path . '/' . $item->{$use_db_field}); + $item->{$use_db_field} = null; } catch (\Exception $e) { \Log::debug($e); @@ -163,7 +154,6 @@ class ImageUploadRequest extends Request } - return $item; } } diff --git a/resources/views/partials/forms/edit/image-upload.blade.php b/resources/views/partials/forms/edit/image-upload.blade.php index d0c89bee99..1676ce669e 100644 --- a/resources/views/partials/forms/edit/image-upload.blade.php +++ b/resources/views/partials/forms/edit/image-upload.blade.php @@ -6,7 +6,7 @@ From fa2dfc3e8774950d4c4e9683a4ecddef48008629 Mon Sep 17 00:00:00 2001 From: snipe Date: Wed, 26 Aug 2020 11:59:30 -0700 Subject: [PATCH 2/9] Use updated request signature for models --- app/Http/Controllers/AssetModelsController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/AssetModelsController.php b/app/Http/Controllers/AssetModelsController.php index 4a4478464f..29914b40a2 100755 --- a/app/Http/Controllers/AssetModelsController.php +++ b/app/Http/Controllers/AssetModelsController.php @@ -85,7 +85,7 @@ class AssetModelsController extends Controller $model->fieldset_id = e($request->input('custom_fieldset')); } - $model = $request->handleImages($model,600,'image', public_path().'/uploads/models', 'image'); + $model = $request->handleImages($model); // Was it created? if ($model->save()) { From 1ff7e6b834f7795db461c6386ec0385db1fb5cf9 Mon Sep 17 00:00:00 2001 From: snipe Date: Wed, 26 Aug 2020 12:17:18 -0700 Subject: [PATCH 3/9] Use Storage facade for user presenter --- app/Presenters/UserPresenter.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Presenters/UserPresenter.php b/app/Presenters/UserPresenter.php index c2d0253fa5..b9d539c069 100644 --- a/app/Presenters/UserPresenter.php +++ b/app/Presenters/UserPresenter.php @@ -6,6 +6,7 @@ use App\Helpers\Helper; use App\Models\Setting; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Gate; +use Illuminate\Support\Facades\Storage; /** * Class UserPresenter @@ -320,8 +321,9 @@ class UserPresenter extends Presenter public function gravatar() { + if ($this->avatar) { - return config('app.url').'/uploads/avatars/'.$this->avatar; + return Storage::disk('public')->url('avatars/'.e($this->avatar)); } if (Setting::getSettings()->load_remote=='1') { From a86dc526958083d89f3129750cd154e402376ea9 Mon Sep 17 00:00:00 2001 From: snipe Date: Wed, 26 Aug 2020 12:17:36 -0700 Subject: [PATCH 4/9] Added fieldname to file name for better clarity on setting logos --- app/Http/Requests/ImageUploadRequest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Http/Requests/ImageUploadRequest.php b/app/Http/Requests/ImageUploadRequest.php index 39b61be45a..2b43f9194d 100644 --- a/app/Http/Requests/ImageUploadRequest.php +++ b/app/Http/Requests/ImageUploadRequest.php @@ -86,9 +86,10 @@ class ImageUploadRequest extends Request $image = $this->file($form_fieldname); $ext = $image->getClientOriginalExtension(); - $file_name = $type.'-'.str_random(18).'.'.$ext; + $file_name = $type.'-'.$form_fieldname.'-'.str_random(10).'.'.$ext; \Log::info('File name will be: '.$file_name); + \Log::debug('File extension is: '. $ext); if ($image->getClientOriginalExtension()!=='svg') { \Log::debug('Not an SVG - resize'); @@ -101,6 +102,7 @@ class ImageUploadRequest extends Request // This requires a string instead of an object, so we use ($string) Storage::disk('public')->put($path.'/'.$file_name, (string)$upload->encode()); + // If the file is an SVG, we need to clean it and NOT encode it } else { \Log::debug('This is an SVG'); From 24be73f76b86e94e020233d15b9537f113515c78 Mon Sep 17 00:00:00 2001 From: snipe Date: Wed, 26 Aug 2020 12:17:50 -0700 Subject: [PATCH 5/9] Use image handling request for logo uploads --- app/Http/Controllers/SettingsController.php | 52 +++------------------ 1 file changed, 7 insertions(+), 45 deletions(-) diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index 320f12dfca..422e9a83c9 100755 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -409,8 +409,8 @@ class SettingsController extends Controller $setting->custom_css = $request->input('custom_css'); } + $setting = $request->handleImages($setting,600,'logo','', 'logo'); - $setting = $request->handleImages('logo',600,'logo','', 'logo'); if ('1' == $request->input('clear_logo')) { Storage::disk('public')->delete($setting->logo); @@ -419,66 +419,33 @@ class SettingsController extends Controller } - if ($request->hasFile('email_logo')) { - $email_image = $email_upload = $request->file('email_logo'); - $email_ext = $email_image->getClientOriginalExtension(); - $setting->email_logo = $email_file_name = 'email_logo.' . $email_ext; + $setting = $request->handleImages($setting,600,'email_logo','', 'email_logo'); - if ('svg' != $email_image->getClientOriginalExtension()) { - $email_upload = Image::make($email_image->getRealPath())->resize(null, 100, function ($constraint) { - $constraint->aspectRatio(); - $constraint->upsize(); - }); - } - // This requires a string instead of an object, so we use ($string) - Storage::disk('public')->put($email_file_name, (string) $email_upload->encode()); - - // Remove Current image if exists - if (($setting->email_logo) && (file_exists($email_file_name))) { - Storage::disk('public')->delete($email_file_name); - } - } elseif ('1' == $request->input('clear_email_logo')) { + if ('1' == $request->input('clear_email_logo')) { Storage::disk('public')->delete($setting->email_logo); $setting->email_logo = null; // If they are uploading an image, validate it and upload it } - // If the user wants to clear the label logo... - if ($request->hasFile('label_logo')) { - $image = $request->file('label_logo'); - $ext = $image->getClientOriginalExtension(); - $setting->label_logo = $label_file_name = 'label_logo.' . $ext; + $setting = $request->handleImages($setting,600,'label_logo','', 'label_logo'); - if ('svg' != $image->getClientOriginalExtension()) { - $upload = Image::make($image->getRealPath())->resize(null, 100, function ($constraint) { - $constraint->aspectRatio(); - $constraint->upsize(); - }); - } - // This requires a string instead of an object, so we use ($string) - Storage::disk('public')->put($label_file_name, (string) $upload->encode()); - - // Remove Current image if exists - if (($setting->label_logo) && (file_exists($label_file_name))) { - Storage::disk('public')->delete($label_file_name); - } - } elseif ('1' == $request->input('clear_label_logo')) { + if ('1' == $request->input('clear_label_logo')) { Storage::disk('public')->delete($setting->label_logo); $setting->label_logo = null; - // If they are uploading an image, validate it and upload it } + // If the user wants to clear the favicon... if ($request->hasFile('favicon')) { $favicon_image = $favicon_upload = $request->file('favicon'); $favicon_ext = $favicon_image->getClientOriginalExtension(); $setting->favicon = $favicon_file_name = 'favicon-uploaded.' . $favicon_ext; - if (('ico' != $favicon_image->getClientOriginalExtension()) && ('svg' != $favicon_image->getClientOriginalExtension())) { + if (($favicon_image->getClientOriginalExtension()!='ico') && ($favicon_image->getClientOriginalExtension()!='svg')) { $favicon_upload = Image::make($favicon_image->getRealPath())->resize(null, 36, function ($constraint) { $constraint->aspectRatio(); $constraint->upsize(); @@ -491,11 +458,6 @@ class SettingsController extends Controller } - - - - - // Remove Current image if exists if (($setting->favicon) && (file_exists($favicon_file_name))) { Storage::disk('public')->delete($favicon_file_name); From bc5a82e734ca9d3bb2ace1c0430631e194819121 Mon Sep 17 00:00:00 2001 From: snipe Date: Fri, 28 Aug 2020 14:10:28 -0700 Subject: [PATCH 6/9] Small fixed for file mover artisan command --- app/Console/Commands/MoveUploadsToNewDisk.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/Console/Commands/MoveUploadsToNewDisk.php b/app/Console/Commands/MoveUploadsToNewDisk.php index 18ed9625e0..6794a09f4d 100644 --- a/app/Console/Commands/MoveUploadsToNewDisk.php +++ b/app/Console/Commands/MoveUploadsToNewDisk.php @@ -20,7 +20,7 @@ class MoveUploadsToNewDisk extends Command * * @var string */ - protected $description = 'This will move your uploaded files to whatever your current disk is.'; + protected $description = 'This will move your locally uploaded files to whatever your current disk is.'; /** * Create a new command instance. @@ -42,7 +42,7 @@ class MoveUploadsToNewDisk extends Command if (config('filesystems.default')=='local') { $this->error('Your current disk is set to local so we cannot proceed.'); - $this->warn("Please configure your .env settings for S3. \nChange your PUBLIC_FILESYSTEM_DISK value to 's3_public' and your PRIVATE_FILESYSTEM_DISK to s3_public."); + $this->warn("Please configure your .env settings for S3. \nChange your PUBLIC_FILESYSTEM_DISK value to 's3_public' and your PRIVATE_FILESYSTEM_DISK to s3_private."); return false; } $delete_local = $this->argument('delete_local'); @@ -84,9 +84,9 @@ class MoveUploadsToNewDisk extends Command } - $logos = glob('public/uploads'."/logo*.*"); + $logos = glob('public/uploads'."/setting*.*"); $this->info("\nThere are ".count($logos).' files that might be logos.'); - $type_count=0; + $type_count = 0; for ($l = 0; $l < count($logos); $l++) { $type_count++; @@ -102,6 +102,7 @@ class MoveUploadsToNewDisk extends Command $private_uploads['imports'] = glob('storage/private_uploads/imports'."/*.*"); $private_uploads['licenses'] = glob('storage/private_uploads/licenses'."/*.*"); $private_uploads['users'] = glob('storage/private_uploads/users'."/*.*"); + $private_uploads['backups'] = glob('storage/private_uploads/users'."/*.*"); foreach($private_uploads as $private_type => $private_upload) From 5aaa2430b4f6dc22666d7def23d3a5f14be94eba Mon Sep 17 00:00:00 2001 From: snipe Date: Fri, 28 Aug 2020 14:10:43 -0700 Subject: [PATCH 7/9] Fixes for backups --- app/Http/Controllers/SettingsController.php | 23 ++++++++++++++------- config/backup.php | 9 ++++---- config/filesystems.php | 5 +++++ 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index 422e9a83c9..9082f7223a 100755 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -1008,19 +1008,28 @@ class SettingsController extends Controller */ public function getBackups() { - $path = storage_path() . '/app/' . config('backup.backup.name'); - $path = 'backups'; + $path = 'app/backups'; $backup_files = Storage::files($path); $files = []; if (count($backup_files) > 0) { for ($f = 0; $f < count($backup_files); ++$f) { - $files[] = [ - 'filename' => basename($backup_files[$f]), - 'filesize' => Setting::fileSizeConvert(Storage::size($backup_files[$f])), - 'modified' => Storage::lastModified($backup_files[$f]), - ]; + + // Skip dotfiles like .gitignore and .DS_STORE + if ((substr(basename($backup_files[$f]), 0, 1) != '.')) { + \Log::debug(basename($backup_files[$f])); + \Log::debug($backup_files[$f]. ' is dotfileish?'); + + $files[] = [ + 'filename' => basename($backup_files[$f]), + 'filesize' => Setting::fileSizeConvert(Storage::size($backup_files[$f])), + 'modified' => Storage::lastModified($backup_files[$f]), + ]; + + } + + } } diff --git a/config/backup.php b/config/backup.php index f4d0f3b975..dd5714957f 100644 --- a/config/backup.php +++ b/config/backup.php @@ -12,7 +12,6 @@ // This is janky, but necessary to figure out whether to include the .env in the backup $included_dirs = [ base_path('public/uploads'), - base_path('config'), base_path('storage/private_uploads'), base_path('storage/oauth-private.key'), base_path('storage/oauth-public.key'), @@ -78,7 +77,7 @@ return [ * For a complete list of available customization options, see https://github.com/spatie/db-dumper */ 'databases' => [ - 'mysql', + env('DB_CONNECTION', 'mysql'), ], ], @@ -106,7 +105,7 @@ return [ * The disk names on which the backups will be stored. */ 'disks' => [ - 'local', + 'backup', ], ], @@ -167,7 +166,7 @@ return [ 'monitorBackups' => [ [ 'name' => config('app.name'), - 'disks' => ['local'], + 'disks' => [env('PRIVATE_FILESYSTEM_DISK', 'local')], 'newestBackupsShouldNotBeOlderThanDays' => 1, 'storageUsedMayNotBeHigherThanMegabytes' => 5000, ], @@ -214,7 +213,7 @@ return [ /* * The number of months for which one monthly backup must be kept. */ - 'keepMonthlyBackupsForMonths' => 4, + 'keepMonthlyBackupsForMonths' => 3, /* * The number of years for which one yearly backup must be kept. diff --git a/config/filesystems.php b/config/filesystems.php index 1294721bbf..b4bf6303e4 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -92,6 +92,11 @@ $config = [ 'url_type' => env('RACKSPACE_URL_TYPE'), ], + 'backup' => [ + 'driver' => env('PRIVATE_FILESYSTEM_DISK', 'local'), + 'root' => storage_path('app'), + ], + ], ]; From e1cc003cec69085201526b5aa263765674dee310 Mon Sep 17 00:00:00 2001 From: snipe Date: Fri, 28 Aug 2020 18:22:37 -0700 Subject: [PATCH 8/9] Fixed backup urls for download --- app/Http/Controllers/SettingsController.php | 11 +++++------ resources/views/settings/backups.blade.php | 6 +++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index 9082f7223a..68943f912e 100755 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -1018,8 +1018,6 @@ class SettingsController extends Controller // Skip dotfiles like .gitignore and .DS_STORE if ((substr(basename($backup_files[$f]), 0, 1) != '.')) { - \Log::debug(basename($backup_files[$f])); - \Log::debug($backup_files[$f]. ' is dotfileish?'); $files[] = [ 'filename' => basename($backup_files[$f]), @@ -1081,9 +1079,11 @@ class SettingsController extends Controller */ public function downloadFile($filename = null) { + $path = 'app/backups'; + if (! config('app.lock_passwords')) { - if (Storage::exists($filename)) { - return Response::download(Storage::url('') . e($filename)); + if (Storage::exists($path . '/' . $filename)) { + return Storage::download($path . '/' . $filename); } else { // Redirect to the backup page return redirect()->route('settings.backups.index')->with('error', trans('admin/settings/message.backup.file_not_found')); @@ -1106,12 +1106,11 @@ class SettingsController extends Controller public function deleteFile($filename = null) { if (! config('app.lock_passwords')) { - $path = 'backups'; + $path = 'app/backups'; if (Storage::exists($path . '/' . $filename)) { try { Storage::delete($path . '/' . $filename); - return redirect()->route('settings.backups.index')->with('success', trans('admin/settings/message.backup.file_deleted')); } catch (\Exception $e) { \Log::debug($e); diff --git a/resources/views/settings/backups.blade.php b/resources/views/settings/backups.blade.php index 4e36f7050d..bea2a5bd44 100644 --- a/resources/views/settings/backups.blade.php +++ b/resources/views/settings/backups.blade.php @@ -29,7 +29,11 @@ @foreach ($files as $file) - {{ $file['filename'] }} + + + {{ $file['filename'] }} + + {{ date("M d, Y g:i A", $file['modified']) }} {{ $file['filesize'] }} From f2c60d055b3db13d913234d54f6b17ee35b213ee Mon Sep 17 00:00:00 2001 From: snipe Date: Fri, 28 Aug 2020 18:22:57 -0700 Subject: [PATCH 9/9] Updated docblock --- app/Http/Controllers/SettingsController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index 68943f912e..fb412c6e1e 100755 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -1075,7 +1075,7 @@ class SettingsController extends Controller * * @since [v1.8] * - * @return Redirect + * @return Storage */ public function downloadFile($filename = null) {