From 79367642b1b7bede0add08c982a29aea44382f98 Mon Sep 17 00:00:00 2001 From: snipe <snipe@snipe.net> Date: Sat, 29 Sep 2018 21:33:52 -0700 Subject: [PATCH] [WIP] Added #5957 - Flysystem support (#6262) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added AWS url to example env * Upgrader - added check for new storage path and attempt to move * Ignore symlink * Updated paths for models * Moved copy methods * Added AWS_URL support For some reasin, Flysystem was generating the wrong AWS url (with a region included) * Switch to Flysystem for image uploads * Nicer display of image preview * Updated image preview on edit blades to use Flysystem * Twiddled some more paths * Working filesystems config * Updated Asset Models and Departments to use Flysystem * Janky workaround for differing S3/local urls/paths * Try to smartly use S3 as public disk if S3 is configured * Use public disk Storage options for public files * Additional transformer edits for Flysystem * Removed debugging * Added missing use Storage directive * Updated seeders to use Flysystem * Default logo * Set a default width We can potentially override this in settings later * Use Flysystem for logo upload * Update downloadFile to use Flysystem * Updated AssetFilesController to use Flysystem * Updated acceptance signatures to use Flysystem * Updated signature view to use Flysystem This isn’t working 100% yet * Use Flysystem facade for displaying asset image * Set assets path Should clean all these up when we’re done here * Added Rackspace support for Flysystem * Added Flysystem migrator console command * Added use Storage directive for categories * Added user avatars to Flysystem * Added profile avatar to Flysystem * Added the option to delete local files with the migrator * Added a check to prevent people from trying to move from local to local * Fixed the selectlists for Flysystem * Fixed the getImageUrl method to reflect Flysystem * Fixed AWS copy process * Fixed models path * More selectlist updates for Flysystem * Updated example .envs with updated env variable names * *sigh* * Updated non-asset getImageUrl() methods to use Flysystem * Removed S3 hardcoding * Use Flysystem in email headers * Fixed typo * Removed camera support from asset file upload We’ll find a way to add this in later (and add that support to all of the other image uploads as well) * Fixed path for categories * WIP - Switched to standard handleImages for asset upload. This is currently broken as I refact the handleImages method. Because the assets store/create methods use their own Form Request, the handleImages method doesn’t exist in that Form Request so it wil error now. * Fixed css URL error * Updated Debugbar to latest version (#6265) v3.2 adds support for Laravel 5.7 * Fixed: Missing CSS file in basic.blade.php (#6264) * Fixed missing CSS file in basic.blade.php * Added * Changed stylesheet import for authorize.blade.php * Updated composer lock * Added AWS_BUCKET_ROOT as env variable * Use nicer image preview for logo upload * Removed AssetRequest form request * Removed asset form request, moved custom field validation into model * Added additional help text for logo upload * Increased the size of the image resize - should make this a setting tho * Few more formatting tweaks to logo section of branding blade preview * Use Flysystem for asset/license file uploads * Use Flysystem for removing images from models that have been deleted * Enable backups to use Flysystem This only handles part of the problem. This just makes it so we can ship files to S3 if we want, but does not account for how we backup files that are hosted on S3 * Use Flysystem to download license files * Updated audits to use Flysystem --- .env.example | 8 +- .env.testing | 8 +- .env.testing-ci | 8 +- app/Console/Commands/MoveUploadsToNewDisk.php | 183 ++++++++ app/Console/Kernel.php | 1 + .../Accessories/AccessoriesController.php | 10 + .../Account/AcceptanceController.php | 15 +- .../Controllers/Api/AssetModelsController.php | 5 +- app/Http/Controllers/Api/AssetsController.php | 5 +- .../Controllers/Api/CategoriesController.php | 3 +- .../Controllers/Api/CompaniesController.php | 2 +- .../Api/CustomFieldsetsController.php | 1 - .../Controllers/Api/DepartmentsController.php | 3 +- app/Http/Controllers/Api/ImportController.php | 3 +- .../Controllers/Api/LocationsController.php | 3 +- .../Api/ManufacturersController.php | 3 +- .../Controllers/Api/SuppliersController.php | 3 +- app/Http/Controllers/Api/UsersController.php | 9 + .../Controllers/AssetModelsController.php | 6 +- .../Assets/AssetFilesController.php | 26 +- .../Controllers/Assets/AssetsController.php | 118 ++--- app/Http/Controllers/CategoriesController.php | 3 +- app/Http/Controllers/CompaniesController.php | 10 + .../Components/ComponentsController.php | 11 + .../Controllers/DepartmentsController.php | 9 + .../Licenses/LicenseFilesController.php | 45 +- app/Http/Controllers/LocationsController.php | 12 +- .../Controllers/ManufacturersController.php | 7 +- app/Http/Controllers/ProfileController.php | 39 +- app/Http/Controllers/SettingsController.php | 84 ++-- app/Http/Controllers/SuppliersController.php | 1 + app/Http/Requests/AssetRequest.php | 68 --- app/Http/Requests/ImageUploadRequest.php | 36 +- .../Transformers/AccessoriesTransformer.php | 4 +- .../Transformers/ActionlogsTransformer.php | 2 +- .../Transformers/AssetModelsTransformer.php | 3 +- .../Transformers/CategoriesTransformer.php | 3 +- .../Transformers/CompaniesTransformer.php | 3 +- .../Transformers/ComponentsTransformer.php | 3 +- .../Transformers/ConsumablesTransformer.php | 3 +- .../Transformers/DepartmentsTranformer.php | 3 +- .../Transformers/LocationsTransformer.php | 3 +- .../Transformers/ManufacturersTransformer.php | 3 +- .../Transformers/SuppliersTransformer.php | 3 +- app/Models/Accessory.php | 3 +- app/Models/Asset.php | 32 +- app/Models/AssetModel.php | 3 +- app/Models/Consumable.php | 3 +- app/Presenters/UserPresenter.php | 3 +- app/Providers/SettingsServiceProvider.php | 41 +- composer.json | 1 + composer.lock | 406 ++++++++++++++---- config/backup.php | 7 +- config/filesystems.php | 83 ++-- database/seeds/AssetModelSeeder.php | 26 +- database/seeds/AssetSeeder.php | 15 +- database/seeds/CompanySeeder.php | 27 +- database/seeds/LocationSeeder.php | 27 +- database/seeds/ManufacturerSeeder.php | 26 +- public/img/demo/logo.png | Bin 0 -> 22482 bytes resources/lang/en/general.php | 1 + resources/views/accessories/edit.blade.php | 2 +- resources/views/account/profile.blade.php | 2 +- resources/views/categories/edit.blade.php | 2 +- resources/views/companies/edit.blade.php | 2 +- resources/views/components/edit.blade.php | 2 +- resources/views/consumables/edit.blade.php | 2 +- resources/views/departments/edit.blade.php | 2 +- resources/views/hardware/edit.blade.php | 131 +----- resources/views/hardware/view.blade.php | 4 +- resources/views/layouts/basic.blade.php | 2 +- resources/views/layouts/default.blade.php | 4 +- resources/views/locations/edit.blade.php | 4 +- resources/views/manufacturers/edit.blade.php | 2 +- resources/views/models/edit.blade.php | 2 +- .../forms/edit/image-upload.blade.php | 4 +- resources/views/settings/backups.blade.php | 2 +- resources/views/settings/branding.blade.php | 32 +- resources/views/users/view.blade.php | 6 +- .../views/vendor/mail/html/header.blade.php | 4 +- upgrade.php | 59 ++- 81 files changed, 1093 insertions(+), 662 deletions(-) create mode 100644 app/Console/Commands/MoveUploadsToNewDisk.php delete mode 100644 app/Http/Requests/AssetRequest.php create mode 100644 public/img/demo/logo.png diff --git a/.env.example b/.env.example index f6ad25fc1..dee4985cc 100644 --- a/.env.example +++ b/.env.example @@ -85,10 +85,12 @@ REDIS_PORT-null # -------------------------------------------- # OPTIONAL: AWS S3 SETTINGS # -------------------------------------------- -AWS_SECRET=null -AWS_KEY=null -AWS_REGION=null +AWS_SECRET_ACCESS_KEY=null +AWS_ACCESS_KEY_ID=null +AWS_DEFAULT_REGION=null AWS_BUCKET=null +AWS_BUCKET_ROOT=null +AWS_URL=null # -------------------------------------------- # OPTIONAL: LOGIN THROTTLING diff --git a/.env.testing b/.env.testing index f84dd82e9..980f24f09 100644 --- a/.env.testing +++ b/.env.testing @@ -40,10 +40,12 @@ IMAGE_LIB=gd # -------------------------------------------- # OPTIONAL: AWS S3 SETTINGS # -------------------------------------------- -AWS_SECRET=null -AWS_KEY=null -AWS_REGION=null +AWS_SECRET_ACCESS_KEY=null +AWS_ACCESS_KEY_ID=null +AWS_DEFAULT_REGION=null AWS_BUCKET=null +AWS_BUCKET_ROOT=null +AWS_URL=null # -------------------------------------------- diff --git a/.env.testing-ci b/.env.testing-ci index b12a99d44..978c1895a 100644 --- a/.env.testing-ci +++ b/.env.testing-ci @@ -40,10 +40,12 @@ IMAGE_LIB=gd # -------------------------------------------- # OPTIONAL: AWS S3 SETTINGS # -------------------------------------------- -AWS_SECRET=null -AWS_KEY=null -AWS_REGION=null +AWS_SECRET_ACCESS_KEY=null +AWS_ACCESS_KEY_ID=null +AWS_DEFAULT_REGION=null AWS_BUCKET=null +AWS_BUCKET_ROOT=null +AWS_URL=null # -------------------------------------------- diff --git a/app/Console/Commands/MoveUploadsToNewDisk.php b/app/Console/Commands/MoveUploadsToNewDisk.php new file mode 100644 index 000000000..505e260ce --- /dev/null +++ b/app/Console/Commands/MoveUploadsToNewDisk.php @@ -0,0 +1,183 @@ +<?php + +namespace App\Console\Commands; + + +use Illuminate\Console\Command; +use Illuminate\Support\Facades\Storage; + +class MoveUploadsToNewDisk extends Command +{ + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'snipeit:move-uploads {delete_local?}'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'This will move your uploaded files to whatever your current disk is.'; + + /** + * Create a new command instance. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + + 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 or Rackspace, \nand change your FILESYSTEM_DISK value to 's3' or 'rackspace'."); + return false; + } + $delete_local = $this->argument('delete_local'); + + $public_uploads['accessories'] = glob('storage/app/public/accessories'."/*.*"); + $public_uploads['assets'] = glob('storage/app/public/assets'."/*.*"); + $public_uploads['avatars'] = glob('storage/app/public/avatars'."/*.*"); + $public_uploads['barcodes'] = glob('storage/app/public/barcodes'."/*.*"); + $public_uploads['categories'] = glob('storage/app/public/categories'."/*.*"); + $public_uploads['companies'] = glob('storage/app/public/companies'."/*.*"); + $public_uploads['components'] = glob('storage/app/public/components'."/*.*"); + $public_uploads['consumables'] = glob('storage/app/public/consumables'."/*.*"); + $public_uploads['departments'] = glob('storage/app/public/departments'."/*.*"); + $public_uploads['locations'] = glob('storage/app/public/locations'."/*.*"); + $public_uploads['manufacturers'] = glob('storage/app/public/manufacturers'."/*.*"); + $public_uploads['suppliers'] = glob('storage/app/public/suppliers'."/*.*"); + $public_uploads['assetmodels'] = glob('storage/app/public/models'."/*.*"); + + + // iterate files + foreach($public_uploads as $public_type => $public_upload) + { + $type_count = 0; + $this->info("\nThere are ".count($public_upload).' PUBLIC '.$public_type.' files.'); + + for ($i = 0; $i < count($public_upload); $i++) { + $type_count++; + $filename = basename($public_upload[$i]); + + try { + Storage::disk('public')->put($public_type.'/'.$filename, file_get_contents($public_upload[$i])); + $new_url = Storage::disk('public')->url($public_type.'/'.$filename, $filename); + $this->info($type_count.'. PUBLIC: '.$filename.' was copied to '.$new_url); + } catch (\Exception $e) { + \Log::debug($e); + $this->error($e); + } + + } + + } + + $logos = glob('public/uploads'."/logo*.*"); + $this->info("\nThere are ".count($logos).' files that might be logos.'); + $type_count=0; + + for ($l = 0; $l < count($logos); $l++) { + $type_count++; + $filename = basename($logos[$l]); + $new_url = Storage::disk('public')->url($logos[$l], file_get_contents($public_upload[$i])); + $this->info($type_count.'. LOGO: '.$filename.' was copied to '.$new_url); + } + + $private_uploads['assets'] = glob('storage/private_uploads/assets'."/*.*"); + $private_uploads['signatures'] = glob('storage/private_uploads/signatures'."/*.*"); + $private_uploads['audits'] = glob('storage/private_uploads/audits'."/*.*"); + $private_uploads['assetmodels'] = glob('storage/private_uploads/assetmodels'."/*.*"); + $private_uploads['imports'] = glob('storage/private_uploads/imports'."/*.*"); + $private_uploads['licenses'] = glob('storage/private_uploads/licenses'."/*.*"); + $private_uploads['users'] = glob('storage/private_uploads/users'."/*.*"); + + + foreach($private_uploads as $private_type => $private_upload) + { + $this->info("\nThere are ".count($private_upload).' PRIVATE '.$private_type.' files.'); + // $this->info(print_r($private_upload, true)); + + $type_count = 0; + for ($x = 0; $x < count($private_upload); $x++) { + $type_count++; + $filename = basename($private_upload[$x]); + + try { + Storage::disk('private_uploads')->put($private_type.'/'.$filename, file_get_contents($public_upload[$i])); + $new_url = Storage::url($private_type.'/'.$filename, $filename); + $this->info($type_count.'. PRIVATE: '.$filename.' was copied to '.$new_url); + + } catch (\Exception $e) { + \Log::debug($e); + $this->error($e); + } + + } + + } + + + if ($delete_local=='true') { + $public_delete_count = 0; + $private_delete_count = 0; + + $this->info("\n\n"); + $this->error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'); + $this->warn("\nTHIS WILL DELETE ALL OF YOUR LOCAL UPLOADED FILES. \n\nThis cannot be undone, so you should take a backup of your system before you proceed.\n"); + $this->error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'); + + if ($this->confirm("Do you wish to continue?")) { + + foreach($public_uploads as $public_type => $public_upload) { + + for ($i = 0; $i < count($public_upload); $i++) { + $filename = $public_upload[$i]; + try { + unlink($filename); + $public_delete_count++; + } catch (\Exception $e) { + \Log::debug($e); + $this->error($e); + } + + } + } + + foreach($private_uploads as $private_type => $private_upload) + { + + for ($i = 0; $i < count($private_upload); $i++) { + $filename = $private_upload[$i]; + try { + unlink($filename); + $private_delete_count++; + } catch (\Exception $e) { + \Log::debug($e); + $this->error($e); + } + + } + } + + $this->info($public_delete_count." PUBLIC local files and ".$private_delete_count." PRIVATE local files were delete from your filesystem."); + } + } + + + + + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 750369df5..ad2d6dcfd 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -32,6 +32,7 @@ class Kernel extends ConsoleKernel Commands\SyncAssetCounters::class, Commands\RestoreDeletedUsers::class, Commands\SendCurrentInventoryToUsers::class, + Commands\MoveUploadsToNewDisk::class, ]; /** diff --git a/app/Http/Controllers/Accessories/AccessoriesController.php b/app/Http/Controllers/Accessories/AccessoriesController.php index 53cceb46a..057029706 100755 --- a/app/Http/Controllers/Accessories/AccessoriesController.php +++ b/app/Http/Controllers/Accessories/AccessoriesController.php @@ -9,6 +9,7 @@ use App\Models\Company; use Illuminate\Support\Facades\Auth; use Illuminate\Http\Request; use Redirect; +use Illuminate\Support\Facades\Storage; /** This controller handles all actions related to Accessories for * the Snipe-IT Asset Management application. @@ -170,6 +171,15 @@ class AccessoriesController extends Controller if ($accessory->hasUsers() > 0) { return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.assoc_users', array('count'=> $accessory->hasUsers()))); } + + if ($accessory->image) { + try { + Storage::disk('public')->delete('accessories'.'/'.$accessory->image); + } catch (\Exception $e) { + \Log::debug($e); + } + } + $accessory->delete(); return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.delete.success')); } diff --git a/app/Http/Controllers/Account/AcceptanceController.php b/app/Http/Controllers/Account/AcceptanceController.php index 198e11e99..23cd29d2d 100644 --- a/app/Http/Controllers/Account/AcceptanceController.php +++ b/app/Http/Controllers/Account/AcceptanceController.php @@ -15,6 +15,7 @@ use App\Models\License; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Str; +use Illuminate\Support\Facades\Storage; class AcceptanceController extends Controller { @@ -40,7 +41,7 @@ class AcceptanceController extends Controller { $acceptance = CheckoutAcceptance::find($id); if (is_null($acceptance)) { - return redirect()->reoute('account.accept')->with('error', trans('admin/hardware/message.does_not_exist')); + return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist')); } if (! $acceptance->isPending()) { @@ -70,7 +71,7 @@ class AcceptanceController extends Controller { $acceptance = CheckoutAcceptance::find($id); if (is_null($acceptance)) { - return redirect()->reoute('account.accept')->with('error', trans('admin/hardware/message.does_not_exist')); + return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist')); } if (! $acceptance->isPending()) { @@ -92,13 +93,17 @@ class AcceptanceController extends Controller { /** * Get the signature and save it */ + + if (!Storage::exists('private_uploads/signatures')) Storage::makeDirectory('private_uploads/signatures', 775); + + + if ($request->filled('signature_output')) { - $path = config('app.private_uploads').'/signatures'; $sig_filename = "siglog-" .Str::uuid() . '-'.date('Y-m-d-his').".png"; $data_uri = e($request->input('signature_output')); $encoded_image = explode(",", $data_uri); $decoded_image = base64_decode($encoded_image[1]); - file_put_contents($path."/".$sig_filename, $decoded_image); + Storage::put('private_uploads/signatures/'.$sig_filename, (string)$decoded_image); } @@ -122,4 +127,4 @@ class AcceptanceController extends Controller { return redirect()->to('account/accept')->with('success', $return_msg); } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Api/AssetModelsController.php b/app/Http/Controllers/Api/AssetModelsController.php index c0011b61a..289348f17 100644 --- a/app/Http/Controllers/Api/AssetModelsController.php +++ b/app/Http/Controllers/Api/AssetModelsController.php @@ -9,6 +9,7 @@ use Illuminate\Http\Request; use App\Http\Transformers\AssetModelsTransformer; use App\Http\Transformers\AssetsTransformer; use App\Http\Transformers\SelectlistTransformer; +use Illuminate\Support\Facades\Storage; /** @@ -177,7 +178,7 @@ class AssetModelsController extends Controller if ($assetmodel->image) { try { - unlink(public_path().'/uploads/models/'.$assetmodel->image); + Storage::disk('public')->delete('assetmodels/'.$assetmodel->image); } catch (\Exception $e) { \Log::error($e); } @@ -234,7 +235,7 @@ class AssetModelsController extends Controller $assetmodel->use_text .= ' (#'.e($assetmodel->model_number).')'; } - $assetmodel->use_image = ($settings->modellistCheckedValue('image') && ($assetmodel->image)) ? url('/').'/uploads/models/'.$assetmodel->image : null; + $assetmodel->use_image = ($settings->modellistCheckedValue('image') && ($assetmodel->image)) ? Storage::disk('public')->url('assetmodels/'.e($assetmodel->image)) : null; } return (new SelectlistTransformer)->transformSelectlist($assetmodels); diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index dbed7be79..0b65508c5 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -3,7 +3,6 @@ namespace App\Http\Controllers\Api; use App\Helpers\Helper; use App\Http\Controllers\Controller; -use App\Http\Requests\AssetRequest; use App\Http\Requests\AssetCheckoutRequest; use App\Http\Transformers\AssetsTransformer; use App\Models\Asset; @@ -400,7 +399,7 @@ class AssetsController extends Controller * @since [v4.0] * @return JsonResponse */ - public function store(AssetRequest $request) + public function store(Request $request) { $this->authorize('create', Asset::class); @@ -431,7 +430,7 @@ class AssetsController extends Controller // Update custom fields in the database. // Validation for these fields is handled through the AssetRequest form request $model = AssetModel::find($request->get('model_id')); - if ($model->fieldset) { + if (($model) && ($model->fieldset)) { foreach ($model->fieldset->fields as $field) { $asset->{$field->convertUnicodeDbSlug()} = e($request->input($field->convertUnicodeDbSlug(), null)); } diff --git a/app/Http/Controllers/Api/CategoriesController.php b/app/Http/Controllers/Api/CategoriesController.php index 3a208c64a..1cee93110 100644 --- a/app/Http/Controllers/Api/CategoriesController.php +++ b/app/Http/Controllers/Api/CategoriesController.php @@ -8,6 +8,7 @@ use App\Helpers\Helper; use App\Models\Category; use App\Http\Transformers\CategoriesTransformer; use App\Http\Transformers\SelectlistTransformer; +use Illuminate\Support\Facades\Storage; class CategoriesController extends Controller { @@ -158,7 +159,7 @@ class CategoriesController extends Controller // This lets us have more flexibility in special cases like assets, where // they may not have a ->name value but we want to display something anyway foreach ($categories as $category) { - $category->use_image = ($category->image) ? url('/').'/uploads/categories/'.$category->image : null; + $category->use_image = ($category->image) ? Storage::disk('public')->url('categories/'.$category->image, $category->image) : null; } return (new SelectlistTransformer)->transformSelectlist($categories); diff --git a/app/Http/Controllers/Api/CompaniesController.php b/app/Http/Controllers/Api/CompaniesController.php index d2dc5990c..6b1c63f54 100644 --- a/app/Http/Controllers/Api/CompaniesController.php +++ b/app/Http/Controllers/Api/CompaniesController.php @@ -178,7 +178,7 @@ class CompaniesController extends Controller // This lets us have more flexibility in special cases like assets, where // they may not have a ->name value but we want to display something anyway foreach ($companies as $company) { - $company->use_image = ($company->image) ? url('/').'/uploads/companies/'.$company->image : null; + $company->use_image = ($company->image) ? Storage::disk('public')->url('companies/'.$company->image, $company->image) : null; } return (new SelectlistTransformer)->transformSelectlist($companies); diff --git a/app/Http/Controllers/Api/CustomFieldsetsController.php b/app/Http/Controllers/Api/CustomFieldsetsController.php index f5cfafdf9..5ed916152 100644 --- a/app/Http/Controllers/Api/CustomFieldsetsController.php +++ b/app/Http/Controllers/Api/CustomFieldsetsController.php @@ -16,7 +16,6 @@ use App\Http\Controllers\Controller; use App\Helpers\Helper; use App\Http\Transformers\CustomFieldsTransformer; use App\Http\Transformers\CustomFieldsetsTransformer; -use App\Http\Requests\AssetRequest; /** * This controller handles all actions related to Custom Asset Fieldsets for diff --git a/app/Http/Controllers/Api/DepartmentsController.php b/app/Http/Controllers/Api/DepartmentsController.php index 50a03baff..1d0e11dcf 100644 --- a/app/Http/Controllers/Api/DepartmentsController.php +++ b/app/Http/Controllers/Api/DepartmentsController.php @@ -9,6 +9,7 @@ use App\Http\Transformers\DepartmentsTransformer; use App\Helpers\Helper; use Auth; use App\Http\Transformers\SelectlistTransformer; +use Illuminate\Support\Facades\Storage; class DepartmentsController extends Controller { @@ -152,7 +153,7 @@ class DepartmentsController extends Controller // This lets us have more flexibility in special cases like assets, where // they may not have a ->name value but we want to display something anyway foreach ($departments as $department) { - $department->use_image = ($department->image) ? url('/').'/uploads/departments/'.$department->image : null; + $department->use_image = ($department->image) ? Storage::disk('public')->url('departments/'.$department->image, $department->image) : null; } return (new SelectlistTransformer)->transformSelectlist($departments); diff --git a/app/Http/Controllers/Api/ImportController.php b/app/Http/Controllers/Api/ImportController.php index e18acf3a7..797df4d9e 100644 --- a/app/Http/Controllers/Api/ImportController.php +++ b/app/Http/Controllers/Api/ImportController.php @@ -15,6 +15,7 @@ use League\Csv\Reader; use Symfony\Component\HttpFoundation\File\Exception\FileException; use Artisan; use App\Models\Asset; +use Illuminate\Support\Facades\Storage; class ImportController extends Controller { @@ -167,7 +168,7 @@ class ImportController extends Controller if ($import = Import::find($import_id)) { try { // Try to delete the file - unlink(config('app.private_uploads').'/imports/'.$import->file_path); + Storage::delete('imports/'.$import->file_path); $import->delete(); return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.import.file_delete_success'))); diff --git a/app/Http/Controllers/Api/LocationsController.php b/app/Http/Controllers/Api/LocationsController.php index 452b5b73b..eaabc0ac3 100644 --- a/app/Http/Controllers/Api/LocationsController.php +++ b/app/Http/Controllers/Api/LocationsController.php @@ -8,6 +8,7 @@ use App\Helpers\Helper; use App\Models\Location; use App\Http\Transformers\LocationsTransformer; use App\Http\Transformers\SelectlistTransformer; +use Illuminate\Support\Facades\Storage; class LocationsController extends Controller { @@ -203,7 +204,7 @@ class LocationsController extends Controller // they may not have a ->name value but we want to display something anyway foreach ($locations as $location) { $location->use_text = $location->name; - $location->use_image = ($location->image) ? url('/').'/uploads/locations/'.$location->image : null; + $location->use_image = ($location->image) ? Storage::disk('public')->url('locations/'.$location->image, $location->image): null; } return (new SelectlistTransformer)->transformSelectlist($locations); diff --git a/app/Http/Controllers/Api/ManufacturersController.php b/app/Http/Controllers/Api/ManufacturersController.php index a943cc21d..2d30b3d5f 100644 --- a/app/Http/Controllers/Api/ManufacturersController.php +++ b/app/Http/Controllers/Api/ManufacturersController.php @@ -9,6 +9,7 @@ use App\Models\Manufacturer; use App\Http\Transformers\DatatablesTransformer; use App\Http\Transformers\ManufacturersTransformer; use App\Http\Transformers\SelectlistTransformer; +use Illuminate\Support\Facades\Storage; class ManufacturersController extends Controller { @@ -166,7 +167,7 @@ class ManufacturersController extends Controller // they may not have a ->name value but we want to display something anyway foreach ($manufacturers as $manufacturer) { $manufacturer->use_text = $manufacturer->name; - $manufacturer->use_image = ($manufacturer->image) ? url('/').'/uploads/manufacturers/'.$manufacturer->image : null; + $manufacturer->use_image = ($manufacturer->image) ? Storage::disk('public')->url('manufacturers/'.$manufacturer->image, $manufacturer->image) : null; } return (new SelectlistTransformer)->transformSelectlist($manufacturers); diff --git a/app/Http/Controllers/Api/SuppliersController.php b/app/Http/Controllers/Api/SuppliersController.php index b79fda773..d15492b1b 100644 --- a/app/Http/Controllers/Api/SuppliersController.php +++ b/app/Http/Controllers/Api/SuppliersController.php @@ -8,6 +8,7 @@ use App\Helpers\Helper; use App\Models\Supplier; use App\Http\Transformers\SuppliersTransformer; use App\Http\Transformers\SelectlistTransformer; +use Illuminate\Support\Facades\Storage; class SuppliersController extends Controller @@ -164,7 +165,7 @@ class SuppliersController extends Controller // they may not have a ->name value but we want to display something anyway foreach ($suppliers as $supplier) { $supplier->use_text = $supplier->name; - $supplier->use_image = ($supplier->image) ? url('/').'/uploads/suppliers/'.$supplier->image : null; + $supplier->use_image = ($supplier->image) ? Storage::disk('public')->url('suppliers/'.$supplier->image, $supplier->image) : null; } return (new SelectlistTransformer)->transformSelectlist($suppliers); diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index 7fca6bf3a..f787f4654 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -283,6 +283,15 @@ class UsersController extends Controller return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete_has_assets'))); } + // Remove the user's avatar if they have one + if (Storage::disk('public')->exists('avatars/'.$user->avatar)) { + try { + Storage::disk('public')->delete('avatars/'.$user->avatar); + } catch (\Exception $e) { + \Log::debug($e); + } + } + if ($user->delete()) { return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.delete'))); } diff --git a/app/Http/Controllers/AssetModelsController.php b/app/Http/Controllers/AssetModelsController.php index d97c36e13..790b8c987 100755 --- a/app/Http/Controllers/AssetModelsController.php +++ b/app/Http/Controllers/AssetModelsController.php @@ -82,7 +82,7 @@ class AssetModelsController extends Controller $model->fieldset_id = e($request->input('custom_fieldset')); } - $model = $request->handleImages($model, app('models_upload_path')); + $model = $request->handleImages($model); // Was it created? if ($model->save()) { @@ -161,7 +161,7 @@ class AssetModelsController extends Controller } } - $model = $request->handleImages($model, app('models_upload_path')); + $model = $request->handleImages($model); if ($model->save()) { return redirect()->route("models.index")->with('success', trans('admin/models/message.update.success')); @@ -194,7 +194,7 @@ class AssetModelsController extends Controller if ($model->image) { try { - unlink(public_path().'/uploads/models/'.$model->image); + Storage::disk('public')->delete('models/'.$model->image); } catch (\Exception $e) { \Log::error($e); } diff --git a/app/Http/Controllers/Assets/AssetFilesController.php b/app/Http/Controllers/Assets/AssetFilesController.php index 9f31b80dc..bc7f80813 100644 --- a/app/Http/Controllers/Assets/AssetFilesController.php +++ b/app/Http/Controllers/Assets/AssetFilesController.php @@ -31,13 +31,14 @@ class AssetFilesController extends Controller $this->authorize('update', $asset); if ($request->hasFile('file')) { + + if (!Storage::exists('private_uploads/assets')) Storage::makeDirectory('private_uploads/assets', 775); + foreach ($request->file('file') as $file) { $extension = $file->getClientOriginalExtension(); - $filename = 'hardware-'.$asset->id.'-'.str_random(8); - $filename .= '-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension; - - $file->storeAs('storage/private_uploads/assets', $filename); - $asset->logUpload($filename, e($request->get('notes'))); + $file_name = 'hardware-'.$asset->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension; + Storage::put('private_uploads/assets/'.$file_name, $file); + $asset->logUpload($file_name, e($request->get('notes'))); } return redirect()->back()->with('success', trans('admin/hardware/message.upload.success')); } @@ -67,24 +68,25 @@ class AssetFilesController extends Controller ->header('Content-Type', 'text/plain'); } - $file = $log->get_src('assets'); + $file = 'private_uploads/assets/'.$log->filename; + \Log::debug('Checking for '.$file); if ($log->action_type =='audit') { - $file = $log->get_src('audits'); + $file = 'private_uploads/audits/'.$log->filename; } - if (!file_exists($file)) { + if (!Storage::exists($file)) { return response('File '.$file.' not found on server', 404) ->header('Content-Type', 'text/plain'); } if ($download != 'true') { - if ($contents = file_get_contents($file)) { - return Response::make($contents)->header('Content-Type', mime_content_type($file)); + if ($contents = file_get_contents(Storage::url($file))) { + return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file))); } return JsonResponse::create(["error" => "Failed validation: "], 500); } - return Response::download($file); + return Storage::download($file); } // Prepare the error message $error = trans('admin/hardware/message.does_not_exist', ['id' => $fileId]); @@ -114,7 +116,7 @@ class AssetFilesController extends Controller $this->authorize('update', $asset); $log = Actionlog::find($fileId); if (file_exists(base_path().'/'.$rel_path.'/'.$log->filename)) { - Storage::delete($rel_path.'/'.$log->filename); + Storage::disk('public')->delete($rel_path.'/'.$log->filename); } $log->delete(); return redirect()->back() diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index ce0fa1886..1baf6235a 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -3,7 +3,7 @@ namespace App\Http\Controllers\Assets; use App\Helpers\Helper; use App\Http\Controllers\Controller; -use App\Http\Requests\AssetRequest; +use App\Http\Requests\ImageUploadRequest; use App\Models\Actionlog; use App\Models\Asset; use App\Models\AssetModel; @@ -33,6 +33,7 @@ use TCPDF; use Validator; use View; use App\Models\CheckoutRequest; +use Illuminate\Support\Facades\Storage; /** * This class controls all actions related to assets for @@ -106,7 +107,7 @@ class AssetsController extends Controller * @since [v1.0] * @return Redirect */ - public function store(AssetRequest $request) + public function store(ImageUploadRequest $request) { $this->authorize(Asset::class); @@ -138,48 +139,14 @@ class AssetsController extends Controller $asset->location_id = $request->input('rtd_location_id', null); } - // Create the image (if one was chosen.) - if ($request->hasFile('image')) { - $image = $request->input('image'); - - // After modification, the image is prefixed by mime info like the following: - // data:image/jpeg;base64,; This causes the image library to be unhappy, so we need to remove it. - $header = explode(';', $image, 2)[0]; - // Grab the image type from the header while we're at it. - $extension = substr($header, strpos($header, '/')+1); - // Start reading the image after the first comma, postceding the base64. - $image = substr($image, strpos($image, ',')+1); - - $file_name = str_random(25).".".$extension; - - $directory= public_path('uploads/assets/'); - // Check if the uploads directory exists. If not, try to create it. - if (!file_exists($directory)) { - mkdir($directory, 0755, true); - } - $path = public_path('uploads/assets/'.$file_name); - try { - Image::make($image)->resize(500, 500, function ($constraint) { - $constraint->aspectRatio(); - $constraint->upsize(); - })->save($path); - $asset->image = $file_name; - } catch (\Exception $e) { - \Input::flash(); - $messageBag = new \Illuminate\Support\MessageBag(); - $messageBag->add('image', $e->getMessage()); - \Session()->flash('errors', \Session::get('errors', new \Illuminate\Support\ViewErrorBag) - ->put('default', $messageBag)); - return response()->json(['image' => $e->getMessage()], 422); - } - } + $asset = $request->handleImages($asset); // Update custom fields in the database. // Validation for these fields is handled through the AssetRequest form request $model = AssetModel::find($request->get('model_id')); - if ($model->fieldset) { + if (($model) && ($model->fieldset)) { foreach ($model->fieldset->fields as $field) { if ($field->field_encrypted=='1') { if (Gate::allows('admin')) { @@ -210,12 +177,11 @@ class AssetsController extends Controller $asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset creation', e($request->get('name')), $location); } // Redirect to the asset listing page - \Session::flash('success', trans('admin/hardware/message.create.success')); - return response()->json(['redirect_url' => route('hardware.index')]); + return redirect()->route('hardware.index') + ->with('success', trans('admin/hardware/message.create.success')); } - \Input::flash(); - \Session::flash('errors', $asset->getErrors()); - return response()->json(['errors' => $asset->getErrors()], 500); + return redirect()->back()->withInput()->withErrors($asset->getErrors()); + } /** @@ -293,7 +259,7 @@ class AssetsController extends Controller * @return Redirect */ - public function update(AssetRequest $request, $assetId = null) + public function update(ImageUploadRequest $request, $assetId = null) { // Check if the asset exists if (!$asset = Asset::find($assetId)) { @@ -338,38 +304,7 @@ class AssetsController extends Controller $asset->notes = $request->input('notes'); $asset->physical = '1'; - // Update the image - if ($request->filled('image')) { - $image = $request->input('image'); - // See postCreate for more explaination of the following. - $header = explode(';', $image, 2)[0]; - $extension = substr($header, strpos($header, '/')+1); - $image = substr($image, strpos($image, ',')+1); - - $directory= public_path('uploads/assets/'); - // Check if the uploads directory exists. If not, try to create it. - if (!file_exists($directory)) { - mkdir($directory, 0755, true); - } - - $file_name = str_random(25).".".$extension; - $path = public_path('uploads/assets/'.$file_name); - try { - Image::make($image)->resize(500, 500, function ($constraint) { - $constraint->aspectRatio(); - $constraint->upsize(); - })->save($path); - $asset->image = $file_name; - } catch (\Exception $e) { - \Input::flash(); - $messageBag = new \Illuminate\Support\MessageBag(); - $messageBag->add('image', $e->getMessage()); - \Session()->flash('errors', \Session::get('errors', new \Illuminate\Support\ViewErrorBag) - ->put('default', $messageBag)); - return response()->json(['image' => $e->getMessage()], 422); - } - $asset->image = $file_name; - } + $asset = $request->handleImages($asset); // Update custom fields in the database. // Validation for these fields is handlded through the AssetRequest form request @@ -421,6 +356,14 @@ class AssetsController extends Controller ->where('id', $asset->id) ->update(array('assigned_to' => null)); + if ($asset->image) { + try { + Storage::disk('public')->delete('assets'.'/'.$asset->image); + } catch (\Exception $e) { + \Log::debug($e); + } + } + $asset->delete(); return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.delete.success')); @@ -740,7 +683,7 @@ class AssetsController extends Controller } - public function auditStore(AssetFileRequest $request, $id) + public function auditStore(Request $request, $id) { $this->authorize('audit', Asset::class); @@ -773,22 +716,15 @@ class AssetsController extends Controller if ($asset->save()) { + $path = 'private_uploads/audits'; + if (!Storage::exists($path)) Storage::makeDirectory($path, 775); - $filename = ''; - - if ($request->hasFile('image')) { - $file = $request->file('image'); - try { - $destinationPath = config('app.private_uploads').'/audits'; - $extension = $file->getClientOriginalExtension(); - $filename = 'audit-'.$asset->id.'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension; - $file->move($destinationPath, $filename); - } catch (\Exception $e) { - \Log::error($e); - } - } + $upload = $image = $request->file('image'); + $ext = $image->getClientOriginalExtension(); + $file_name = 'audit-'.str_random(18).'.'.$ext; + Storage::putFileAs($path, $upload, $file_name); - $asset->logAudit($request->input('note'), $request->input('location_id'), $filename); + $asset->logAudit($request->input('note'), $request->input('location_id'), $file_name); return redirect()->to("hardware")->with('success', trans('admin/hardware/message.audit.success')); } } diff --git a/app/Http/Controllers/CategoriesController.php b/app/Http/Controllers/CategoriesController.php index 2de5fd9a5..bc643841e 100755 --- a/app/Http/Controllers/CategoriesController.php +++ b/app/Http/Controllers/CategoriesController.php @@ -17,6 +17,7 @@ use Str; use View; use Image; use App\Http\Requests\ImageUploadRequest; +use Illuminate\Support\Facades\Storage; /** * This class controls all actions related to Categories for @@ -182,7 +183,7 @@ class CategoriesController extends Controller return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'component'])); } - + Storage::disk('public')->delete('categories'.'/'.$category->image); $category->delete(); // Redirect to the locations management page return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.success')); diff --git a/app/Http/Controllers/CompaniesController.php b/app/Http/Controllers/CompaniesController.php index 2ca0d1549..1e6a8473d 100644 --- a/app/Http/Controllers/CompaniesController.php +++ b/app/Http/Controllers/CompaniesController.php @@ -5,6 +5,7 @@ use App\Models\Company; use Illuminate\Http\Request; use Image; use App\Http\Requests\ImageUploadRequest; +use Illuminate\Support\Facades\Storage; /** * This controller handles all actions related to Companies for @@ -142,6 +143,15 @@ final class CompaniesController extends Controller } try { + + if ($company->image) { + try { + Storage::disk('public')->delete('companies'.'/'.$company->image); + } catch (\Exception $e) { + \Log::debug($e); + } + } + $company->delete(); return redirect()->route('companies.index') ->with('success', trans('admin/companies/message.delete.success')); diff --git a/app/Http/Controllers/Components/ComponentsController.php b/app/Http/Controllers/Components/ComponentsController.php index 9e7d5d5a3..6839e1604 100644 --- a/app/Http/Controllers/Components/ComponentsController.php +++ b/app/Http/Controllers/Components/ComponentsController.php @@ -7,6 +7,7 @@ use App\Models\Company; use App\Models\Component; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Input; +use Illuminate\Support\Facades\Storage; /** * This class controls all actions related to Components for @@ -159,6 +160,16 @@ class ComponentsController extends Controller } $this->authorize('delete', $component); + + // Remove the image if one exists + if (Storage::disk('public')->exists('components/'.$component->image)) { + try { + Storage::disk('public')->delete('components/'.$component->image); + } catch (\Exception $e) { + \Log::debug($e); + } + } + $component->delete(); return redirect()->route('components.index')->with('success', trans('admin/components/message.delete.success')); } diff --git a/app/Http/Controllers/DepartmentsController.php b/app/Http/Controllers/DepartmentsController.php index 0922c4125..4afcd2a5a 100644 --- a/app/Http/Controllers/DepartmentsController.php +++ b/app/Http/Controllers/DepartmentsController.php @@ -7,6 +7,7 @@ use App\Models\Department; use Illuminate\Support\Facades\Auth; use Image; use App\Http\Requests\ImageUploadRequest; +use Illuminate\Support\Facades\Storage; class DepartmentsController extends Controller { @@ -124,7 +125,15 @@ class DepartmentsController extends Controller return redirect()->to(route('departments.index'))->with('error', trans('admin/departments/message.assoc_users')); } + if ($department->image) { + try { + Storage::disk('public')->delete('departments'.'/'.$department->image); + } catch (\Exception $e) { + \Log::debug($e); + } + } $department->delete(); + return redirect()->back()->with('success', trans('admin/departments/message.delete.success')); } diff --git a/app/Http/Controllers/Licenses/LicenseFilesController.php b/app/Http/Controllers/Licenses/LicenseFilesController.php index a25b15ada..4ff388335 100644 --- a/app/Http/Controllers/Licenses/LicenseFilesController.php +++ b/app/Http/Controllers/Licenses/LicenseFilesController.php @@ -35,16 +35,20 @@ class LicenseFilesController extends Controller $this->authorize('update', $license); if (Input::hasFile('file')) { + + if (!Storage::exists('private_uploads/licenses')) Storage::makeDirectory('private_uploads/licenses', 775); + $upload_success = false; foreach (Input::file('file') as $file) { $extension = $file->getClientOriginalExtension(); - $filename = 'license-'.$license->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension; + $file_name = 'license-'.$license->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension; - $upload_success = $file->storeAs('storage/private_uploads/licenses', $filename); + $upload_success = Storage::put('private_uploads/licenses/'.$file_name, $file); //Log the upload to the log - $license->logUpload($filename, e($request->input('notes'))); + $license->logUpload($file_name, e($request->input('notes'))); } + // This being called from a modal seems to confuse redirect()->back() // It thinks we should go to the dashboard. As this is only used // from the modal at present, hardcode the redirect. Longterm @@ -76,15 +80,20 @@ class LicenseFilesController extends Controller { $license = License::find($licenseId); - $rel_path = 'storage/private_uploads/licenses'; - // the asset is valid if (isset($license->id)) { $this->authorize('update', $license); $log = Actionlog::find($fileId); - if (file_exists(base_path().'/'.$rel_path.'/'.$log->filename)) { - Storage::delete($rel_path.'/'.$log->filename); + + // Remove the file if one exists + if (Storage::exists('licenses/'.$log->filename)) { + try { + Storage::delete('licenses/'.$log->filename); + } catch (\Exception $e) { + \Log::debug($e); + } } + $log->delete(); return redirect()->back() ->with('success', trans('admin/hardware/message.deletefile.success')); @@ -114,34 +123,30 @@ class LicenseFilesController extends Controller // the license is valid if (isset($license->id)) { $this->authorize('view', $license); - $log = Actionlog::find($fileId); - $file = $log->get_src('licenses'); - - if ($file =='') { - return response('File not found on server', 404) + if (!$log = Actionlog::find($fileId)) { + return response('No matching record for that asset/file', 500) ->header('Content-Type', 'text/plain'); } - $mimetype = \File::mimeType($file); + $file = 'private_uploads/licenses/'.$log->filename; + \Log::debug('Checking for '.$file); - - if (!file_exists($file)) { + if (!Storage::exists($file)) { return response('File '.$file.' not found on server', 404) ->header('Content-Type', 'text/plain'); } if ($download != 'true') { - if ($contents = file_get_contents($file)) { - return Response::make($contents)->header('Content-Type', $mimetype); + if ($contents = file_get_contents(Storage::url($file))) { + return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file))); } return JsonResponse::create(["error" => "Failed validation: "], 500); } - return Response::download($file); + return Storage::download($file); } + return redirect()->route('hardware.index')->with('error', trans('admin/licenses/message.does_not_exist', ['id' => $fileId])); - - return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist')); } diff --git a/app/Http/Controllers/LocationsController.php b/app/Http/Controllers/LocationsController.php index 87ff779eb..9314ede8b 100755 --- a/app/Http/Controllers/LocationsController.php +++ b/app/Http/Controllers/LocationsController.php @@ -6,6 +6,7 @@ use App\Models\Location; use Illuminate\Support\Facades\Auth; use Image; use App\Http\Requests\ImageUploadRequest; +use Illuminate\Support\Facades\Storage; /** * This controller handles all actions related to Locations for @@ -89,7 +90,7 @@ class LocationsController extends Controller $location->manager_id = $request->input('manager_id'); $location->user_id = Auth::id(); - $location = $request->handleImages($location); + $location = $request->handleImages($location, 'public/uploads/locations'); if ($location->save()) { return redirect()->route("locations.index")->with('success', trans('admin/locations/message.create.success')); @@ -159,7 +160,7 @@ class LocationsController extends Controller $location->ldap_ou = $request->input('ldap_ou'); $location->manager_id = $request->input('manager_id'); - $location = $request->handleImages($location); + $location = $request->handleImages($location, 'public/uploads/locations'); if ($location->save()) { @@ -198,6 +199,13 @@ class LocationsController extends Controller } + if ($location->image) { + try { + Storage::disk('public')->delete('locations/'.$location->image); + } catch (\Exception $e) { + \Log::error($e); + } + } $location->delete(); return redirect()->to(route('locations.index'))->with('success', trans('admin/locations/message.delete.success')); } diff --git a/app/Http/Controllers/ManufacturersController.php b/app/Http/Controllers/ManufacturersController.php index 1bbd4466d..c99ac10d9 100755 --- a/app/Http/Controllers/ManufacturersController.php +++ b/app/Http/Controllers/ManufacturersController.php @@ -7,6 +7,7 @@ use Illuminate\Support\Facades\Auth; use Redirect; use Illuminate\Http\Request; use Image; +use Illuminate\Support\Facades\Storage; /** * This controller handles all actions related to Manufacturers for @@ -72,7 +73,7 @@ class ManufacturersController extends Controller $manufacturer->support_email = $request->input('support_email'); - $manufacturer = $request->handleImages($manufacturer); + $manufacturer = $request->handleImages($manufacturer,'manufacturers'); @@ -162,9 +163,9 @@ class ManufacturersController extends Controller if ($manufacturer->image) { try { - unlink(public_path().'/uploads/manufacturers/'.$manufacturer->image); + Storage::disk('public')->delete('manufacturers/'.$manufacturer->image); } catch (\Exception $e) { - + \Log::error($e); } } diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 492b65db0..5b0a06ede 100755 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -12,6 +12,7 @@ use Gate; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; use App\Http\Requests\ImageUploadRequest; +use Illuminate\Support\Facades\Storage; /** * This controller handles all actions related to User Profiles for @@ -61,19 +62,39 @@ class ProfileController extends Controller if (Gate::allows('self.edit_location') && (!config('app.lock_passwords'))) { $user->location_id = $request->input('location_id'); } - - if (Input::file('avatar')) { - $image = Input::file('avatar'); - $file_name = str_slug($user->first_name."-".$user->last_name).".".$image->getClientOriginalExtension(); - $path = public_path('uploads/avatars/'.$file_name); - Image::make($image->getRealPath())->resize(84, 84)->save($path); - $user->avatar = $file_name; - } - if (Input::get('avatar_delete') == 1 && Input::file('avatar') == "") { + + if ($request->input('avatar_delete') == 1) { $user->avatar = null; } + + if ($request->hasFile('avatar')) { + $path = 'avatars'; + + if(!Storage::disk('public')->exists($path)) Storage::disk('public')->makeDirectory($path, 775); + + $upload = $image = $request->file('avatar'); + $ext = $image->getClientOriginalExtension(); + $file_name = 'avatar-'.str_random(18).'.'.$ext; + + if ($image->getClientOriginalExtension()!='svg') { + $upload = Image::make($image->getRealPath())->resize(84, 84); + } + + // This requires a string instead of an object, so we use ($string) + Storage::disk('public')->put($path.'/'.$file_name, (string)$upload->encode()); + + // Remove Current image if exists + if (($user->avatar) && (Storage::disk('public')->exists($path.'/'.$user->avatar))) { + Storage::disk('public')->delete($path.'/'.$user->avatar); + } + + $user->avatar = $file_name; + } + + + if ($user->save()) { return redirect()->route('profile')->with('success', 'Account successfully updated'); } diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index 90391a23f..067bbd562 100755 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -24,6 +24,7 @@ use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\SettingsLdapRequest; use App\Helpers\Helper; use App\Notifications\FirstAdminNotification; +use Illuminate\Support\Facades\Storage; /** * This controller handles all actions related to Settings for @@ -418,25 +419,32 @@ class SettingsController extends Controller // If the user wants to clear the logo, reset the brand type if ($request->input('clear_logo')=='1') { + Storage::disk('public')->delete($setting->logo); $setting->logo = null; $setting->brand = 1; + // If they are uploading an image, validate it and upload it } elseif ($request->hasFile('image')) { - if (!config('app.lock_passwords')) { - $image = $request->file('image'); - $file_name = "logo.".$image->getClientOriginalExtension(); - $path = public_path('uploads'); - if ($image->getClientOriginalExtension()!='svg') { - Image::make($image->getRealPath())->resize(null, 150, function ($constraint) { - $constraint->aspectRatio(); - $constraint->upsize(); - })->save($path.'/'.$file_name); - } else { - $image->move($path, $file_name); - } - $setting->logo = $file_name; + $image = $request->file('image'); + $ext = $image->getClientOriginalExtension(); + $setting->logo = $file_name = 'logo.'.$ext; + + if ($image->getClientOriginalExtension()!='svg') { + $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); } } @@ -911,29 +919,22 @@ class SettingsController extends Controller public function getBackups() { - $path = storage_path().'/app/'.config('backup.backup.name'); + $path = 'backups'; + $backup_files = Storage::files($path); + $files = []; - $files = array(); + if (count($backup_files) > 0) { - if ($handle = opendir($path)) { - - /* This is the correct way to loop over the directory. */ - while (false !== ($entry = readdir($handle))) { - clearstatcache(); - if (substr(strrchr($entry, '.'), 1)=='zip') { - $files[] = array( - 'filename' => $entry, - 'filesize' => Setting::fileSizeConvert(filesize($path.'/'.$entry)), - 'modified' => filemtime($path.'/'.$entry) - ); - } + for ($f = 0; $f < count($backup_files); $f++) { + $files[] = array( + 'filename' => basename($backup_files[$f]), + 'filesize' => Setting::fileSizeConvert(Storage::size($backup_files[$f])), + 'modified' => Storage::lastModified($backup_files[$f]) + ); } - closedir($handle); - rsort($files); } - return view('settings/backups', compact('path', 'files')); } @@ -987,12 +988,10 @@ class SettingsController extends Controller public function downloadFile($filename = null) { if (!config('app.lock_passwords')) { - $path = storage_path().'/app/'.config('backup.backup.name'); - $file = $path.'/'.$filename; - if (file_exists($file)) { - return Response::download($file); - } else { + if (Storage::exists($filename)) { + return Response::download(Storage::url('').e($filename)); + } else { // Redirect to the backup page return redirect()->route('settings.backups.index')->with('error', trans('admin/settings/message.backup.file_not_found')); } @@ -1013,14 +1012,17 @@ class SettingsController extends Controller */ public function deleteFile($filename = null) { - if (!config('app.lock_passwords')) { + $path = '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); + } - $path = storage_path().'/app/'.config('backup.backup.name'); - $file = $path.'/'.$filename; - if (file_exists($file)) { - unlink($file); - return redirect()->route('settings.backups.index')->with('success', trans('admin/settings/message.backup.file_deleted')); } else { return redirect()->route('settings.backups.index')->with('error', trans('admin/settings/message.backup.file_not_found')); } diff --git a/app/Http/Controllers/SuppliersController.php b/app/Http/Controllers/SuppliersController.php index 5af0097ee..985c2d892 100755 --- a/app/Http/Controllers/SuppliersController.php +++ b/app/Http/Controllers/SuppliersController.php @@ -6,6 +6,7 @@ use App\Models\Supplier; use Illuminate\Support\Facades\Auth; use Illuminate\Http\Request; use App\Http\Requests\ImageUploadRequest; +use Illuminate\Support\Facades\Storage; /** * This controller handles all actions related to Suppliers for diff --git a/app/Http/Requests/AssetRequest.php b/app/Http/Requests/AssetRequest.php deleted file mode 100644 index 4004dcec3..000000000 --- a/app/Http/Requests/AssetRequest.php +++ /dev/null @@ -1,68 +0,0 @@ -<?php - -namespace App\Http\Requests; - -use App\Http\Requests\Request; -use App\Models\AssetModel; -use Session; - -class AssetRequest extends Request -{ - /** - * Determine if the user is authorized to make this request. - * - * @return bool - */ - public function authorize() - { - return true; - } - - /** - * Get the validation rules that apply to the request. - * - * @return array - */ - public function rules() - { - $rules = [ - 'name' => 'max:255|nullable', - 'model_id' => 'required|integer|exists:models,id', - 'status_id' => 'required|integer|exists:status_labels,id', - 'company_id' => 'integer|nullable', - 'warranty_months' => 'numeric|nullable', - 'physical' => 'integer|nullable', - 'checkout_date' => 'date', - 'checkin_date' => 'date', - 'supplier_id' => 'integer|nullable', - 'status' => 'integer|nullable', - 'purchase_cost' => 'numeric|nullable', - "assigned_user" => 'sometimes:required_without_all:assigned_asset,assigned_location', - "assigned_asset" => 'sometimes:required_without_all:assigned_user,assigned_location', - "assigned_location" => 'sometimes:required_without_all:assigned_user,assigned_asset', - ]; - - $settings = \App\Models\Setting::getSettings(); - - $rules['asset_tag'] = ($settings->auto_increment_assets == '1') ? 'max:255' : 'required'; - - if($this->request->get('model_id') != '') { - $model = AssetModel::find($this->request->get('model_id')); - - if (($model) && ($model->fieldset)) { - $rules += $model->fieldset->validation_rules(); - } - } - - return $rules; - - } - - public function response(array $errors) - { - $this->session()->flash('errors', Session::get('errors', new \Illuminate\Support\ViewErrorBag) - ->put('default', new \Illuminate\Support\MessageBag($errors))); - \Input::flash(); - return parent::response($errors); - } -} diff --git a/app/Http/Requests/ImageUploadRequest.php b/app/Http/Requests/ImageUploadRequest.php index a01df7b4f..19a545331 100644 --- a/app/Http/Requests/ImageUploadRequest.php +++ b/app/Http/Requests/ImageUploadRequest.php @@ -4,6 +4,8 @@ namespace App\Http\Requests; use App\Models\SnipeModel; use Intervention\Image\Facades\Image; +use Storage; +use Illuminate\Support\Facades\File; class ImageUploadRequest extends Request { @@ -41,36 +43,44 @@ 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, $path = null) + public function handleImages($item, $w = 550, $path = null) { + $type = strtolower(class_basename(get_class($item))); + + if(is_null($path)) { + $path = str_plural($type); + } + + if ($this->hasFile('image')) { if (!config('app.lock_passwords')) { - if(is_null($path)) { - $type = strtolower(class_basename(get_class($item))); - $plural = str_plural($type); - $path = public_path('/uploads/'.$plural); - } - $image = $this->file('image'); + + if(!Storage::disk('public')->exists($path)) Storage::disk('public')->makeDirectory($path, 775); + + $upload = $image = $this->file('image'); $ext = $image->getClientOriginalExtension(); $file_name = $type.'-'.str_random(18).'.'.$ext; + if ($image->getClientOriginalExtension()!='svg') { - Image::make($image->getRealPath())->resize(null, 250, function ($constraint) { + $upload = Image::make($image->getRealPath())->resize(null, $w, function ($constraint) { $constraint->aspectRatio(); $constraint->upsize(); - })->save($path.'/'.$file_name); - } else { - $image->move($path, $file_name); + }); } - // Remove Current image if exists. + // This requires a string instead of an object, so we use ($string) + Storage::disk('public')->put($path.'/'.$file_name, (string)$upload->encode()); + + // Remove Current image if exists if (($item->image) && (file_exists($path.'/'.$item->image))) { - unlink($path.'/'.$item->image); + Storage::disk('public')->delete($path.'/'.$file_name); } $item->image = $file_name; } } elseif ($this->input('image_delete')=='1') { + Storage::disk('public')->delete($path.'/'.$item->image); $item->image = null; } return $item; diff --git a/app/Http/Transformers/AccessoriesTransformer.php b/app/Http/Transformers/AccessoriesTransformer.php index a4e935dab..1f828bd1e 100644 --- a/app/Http/Transformers/AccessoriesTransformer.php +++ b/app/Http/Transformers/AccessoriesTransformer.php @@ -5,6 +5,7 @@ use App\Models\Accessory; use Gate; use Illuminate\Database\Eloquent\Collection; use App\Helpers\Helper; +use Illuminate\Support\Facades\Storage; class AccessoriesTransformer { @@ -23,6 +24,7 @@ class AccessoriesTransformer $array = [ 'id' => $accessory->id, 'name' => e($accessory->name), + 'image' => ($accessory->image) ? Storage::disk('public')->url('accessories/'.e($accessory->image)) : null, 'company' => ($accessory->company) ? ['id' => $accessory->company->id,'name'=> e($accessory->company->name)] : null, 'manufacturer' => ($accessory->manufacturer) ? ['id' => $accessory->manufacturer->id,'name'=> e($accessory->manufacturer->name)] : null, 'supplier' => ($accessory->supplier) ? ['id' => $accessory->supplier->id,'name'=> e($accessory->supplier->name)] : null, @@ -36,7 +38,7 @@ class AccessoriesTransformer 'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null, 'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null, 'remaining_qty' => $accessory->numRemaining(), - 'image' => ($accessory->image) ? url('/').'/uploads/accessories/'.e($accessory->image) : null, + 'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'), diff --git a/app/Http/Transformers/ActionlogsTransformer.php b/app/Http/Transformers/ActionlogsTransformer.php index 53ece0558..c5ea16f59 100644 --- a/app/Http/Transformers/ActionlogsTransformer.php +++ b/app/Http/Transformers/ActionlogsTransformer.php @@ -63,7 +63,7 @@ class ActionlogsTransformer ] : null, 'note' => ($actionlog->note) ? e($actionlog->note): null, - 'signature_file' => ($actionlog->accept_signature) ? route('log.signature.view', ['filename' => $actionlog->accept_signature ]) : null, + 'signature_file' => ($actionlog->signature_filename) ? route('log.signature.view', ['filename' => $actionlog->signature_filename ]) : null, 'log_meta' => ($actionlog->log_meta) ? json_decode($actionlog->log_meta): null, diff --git a/app/Http/Transformers/AssetModelsTransformer.php b/app/Http/Transformers/AssetModelsTransformer.php index 2bb0a9e1a..a344e76d0 100644 --- a/app/Http/Transformers/AssetModelsTransformer.php +++ b/app/Http/Transformers/AssetModelsTransformer.php @@ -5,6 +5,7 @@ use App\Models\AssetModel; use Illuminate\Database\Eloquent\Collection; use Gate; use App\Helpers\Helper; +use Illuminate\Support\Facades\Storage; class AssetModelsTransformer { @@ -28,7 +29,7 @@ class AssetModelsTransformer 'id' => (int) $assetmodel->manufacturer->id, 'name'=> e($assetmodel->manufacturer->name) ] : null, - 'image' => ($assetmodel->image!='') ? app('models_upload_url').e($assetmodel->image) : null, + 'image' => ($assetmodel->image!='') ? Storage::disk('public')->url('assetmodels/'.e($assetmodel->image)) : null, 'model_number' => e($assetmodel->model_number), 'depreciation' => ($assetmodel->depreciation) ? [ 'id' => (int) $assetmodel->depreciation->id, diff --git a/app/Http/Transformers/CategoriesTransformer.php b/app/Http/Transformers/CategoriesTransformer.php index 710bf84bc..b93790f77 100644 --- a/app/Http/Transformers/CategoriesTransformer.php +++ b/app/Http/Transformers/CategoriesTransformer.php @@ -5,6 +5,7 @@ use App\Models\Category; use Illuminate\Database\Eloquent\Collection; use Gate; use App\Helpers\Helper; +use Illuminate\Support\Facades\Storage; class CategoriesTransformer { @@ -25,7 +26,7 @@ class CategoriesTransformer $array = [ 'id' => (int) $category->id, 'name' => e($category->name), - 'image' => ($category->image) ? app('categories_upload_url').e($category->image) : null, + 'image' => ($category->image) ? Storage::disk('public')->url('categories/'.e($category->image)) : null, 'category_type' => e($category->category_type), 'eula' => ($category->getEula()) ? true : false, 'checkin_email' => ($category->checkin_email =='1') ? true : false, diff --git a/app/Http/Transformers/CompaniesTransformer.php b/app/Http/Transformers/CompaniesTransformer.php index 96951bdf8..7fac8ee6d 100644 --- a/app/Http/Transformers/CompaniesTransformer.php +++ b/app/Http/Transformers/CompaniesTransformer.php @@ -5,6 +5,7 @@ use App\Models\Company; use Illuminate\Database\Eloquent\Collection; use Gate; use App\Helpers\Helper; +use Illuminate\Support\Facades\Storage; class CompaniesTransformer { @@ -25,7 +26,7 @@ class CompaniesTransformer $array = [ 'id' => (int) $company->id, 'name' => e($company->name), - 'image' => ($company->image) ? app('companies_upload_url').e($company->image) : null, + 'image' => ($company->image) ? Storage::disk('public')->url('companies/'.e($company->image)) : null, "created_at" => Helper::getFormattedDateObject($company->created_at, 'datetime'), "updated_at" => Helper::getFormattedDateObject($company->updated_at, 'datetime'), "assets_count" => (int) $company->assets_count, diff --git a/app/Http/Transformers/ComponentsTransformer.php b/app/Http/Transformers/ComponentsTransformer.php index cf51b55d7..6cd4b98c7 100644 --- a/app/Http/Transformers/ComponentsTransformer.php +++ b/app/Http/Transformers/ComponentsTransformer.php @@ -5,6 +5,7 @@ use App\Models\Component; use Illuminate\Database\Eloquent\Collection; use App\Helpers\Helper; use Gate; +use Illuminate\Support\Facades\Storage; class ComponentsTransformer { @@ -22,7 +23,7 @@ class ComponentsTransformer $array = [ 'id' => (int) $component->id, 'name' => e($component->name), - 'image' => ($component->image) ? e(url('/').'/uploads/components/'.e($component->image)) : null, + 'image' => ($component->image) ? Storage::disk('public')->url('components/'.e($component->image)) : null, 'serial' => ($component->serial) ? e($component->serial) : null, 'location' => ($component->location) ? [ 'id' => (int) $component->location->id, diff --git a/app/Http/Transformers/ConsumablesTransformer.php b/app/Http/Transformers/ConsumablesTransformer.php index 3d68697a5..36ec8d9c2 100644 --- a/app/Http/Transformers/ConsumablesTransformer.php +++ b/app/Http/Transformers/ConsumablesTransformer.php @@ -5,6 +5,7 @@ use App\Models\Consumable; use Illuminate\Database\Eloquent\Collection; use App\Helpers\Helper; use Gate; +use Illuminate\Support\Facades\Storage; class ConsumablesTransformer { @@ -23,7 +24,7 @@ class ConsumablesTransformer $array = [ 'id' => (int) $consumable->id, 'name' => e($consumable->name), - 'image' => ($consumable->image) ? e(url('/').'/uploads/consumables/'.e($consumable->image)) : null, + 'image' => ($consumable->image) ? Storage::disk('public')->url('consumables/'.e($consumable->image)) : null, 'category' => ($consumable->category) ? ['id' => $consumable->category->id, 'name' => e($consumable->category->name)] : null, 'company' => ($consumable->company) ? ['id' => (int) $consumable->company->id, 'name' => e($consumable->company->name)] : null, 'item_no' => e($consumable->item_no), diff --git a/app/Http/Transformers/DepartmentsTranformer.php b/app/Http/Transformers/DepartmentsTranformer.php index f81954f94..a24943b96 100644 --- a/app/Http/Transformers/DepartmentsTranformer.php +++ b/app/Http/Transformers/DepartmentsTranformer.php @@ -5,6 +5,7 @@ use App\Models\Department; use Illuminate\Database\Eloquent\Collection; use Gate; use App\Helpers\Helper; +use Illuminate\Support\Facades\Storage; class DepartmentsTransformer { @@ -25,7 +26,7 @@ class DepartmentsTransformer $array = [ 'id' => (int) $department->id, 'name' => e($department->name), - 'image' => ($department->image) ? app('departments_upload_url').e($department->image) : null, + 'image' => ($department->image) ? Storage::disk('public')->url(app('departments_upload_url').e($department->image)) : null, 'company' => ($department->company) ? [ 'id' => (int) $department->company->id, 'name'=> e($department->company->name) diff --git a/app/Http/Transformers/LocationsTransformer.php b/app/Http/Transformers/LocationsTransformer.php index 5d93db4de..ce018d4f6 100644 --- a/app/Http/Transformers/LocationsTransformer.php +++ b/app/Http/Transformers/LocationsTransformer.php @@ -5,6 +5,7 @@ use App\Models\Location; use Illuminate\Database\Eloquent\Collection; use Gate; use App\Helpers\Helper; +use Illuminate\Support\Facades\Storage; class LocationsTransformer { @@ -33,7 +34,7 @@ class LocationsTransformer $array = [ 'id' => (int) $location->id, 'name' => e($location->name), - 'image' => ($location->image) ? app('locations_upload_url').e($location->image) : null, + 'image' => ($location->image) ? Storage::disk('public')->url('locations/'.e($location->image)) : null, 'address' => ($location->address) ? e($location->address) : null, 'address2' => ($location->address2) ? e($location->address2) : null, 'city' => ($location->city) ? e($location->city) : null, diff --git a/app/Http/Transformers/ManufacturersTransformer.php b/app/Http/Transformers/ManufacturersTransformer.php index 3db65ee9f..5457d1c94 100644 --- a/app/Http/Transformers/ManufacturersTransformer.php +++ b/app/Http/Transformers/ManufacturersTransformer.php @@ -5,6 +5,7 @@ use App\Models\Manufacturer; use Illuminate\Database\Eloquent\Collection; use Gate; use App\Helpers\Helper; +use Illuminate\Support\Facades\Storage; class ManufacturersTransformer { @@ -26,7 +27,7 @@ class ManufacturersTransformer 'id' => (int) $manufacturer->id, 'name' => e($manufacturer->name), 'url' => e($manufacturer->url), - 'image' => ($manufacturer->image) ? app('manufacturers_upload_url').e($manufacturer->image) : null, + 'image' => ($manufacturer->image) ? Storage::disk('public')->url('manufacturers/'.e($manufacturer->image)) : null, 'support_url' => e($manufacturer->support_url), 'support_phone' => e($manufacturer->support_phone), 'support_email' => e($manufacturer->support_email), diff --git a/app/Http/Transformers/SuppliersTransformer.php b/app/Http/Transformers/SuppliersTransformer.php index ea636e4a6..a3bb8c6f2 100644 --- a/app/Http/Transformers/SuppliersTransformer.php +++ b/app/Http/Transformers/SuppliersTransformer.php @@ -5,6 +5,7 @@ use App\Models\Supplier; use Illuminate\Database\Eloquent\Collection; use Gate; use App\Helpers\Helper; +use Illuminate\Support\Facades\Storage; class SuppliersTransformer { @@ -25,7 +26,7 @@ class SuppliersTransformer $array = [ 'id' => (int) $supplier->id, 'name' => e($supplier->name), - 'image' => ($supplier->image) ? app('suppliers_upload_url').e($supplier->image) : null, + 'image' => ($supplier->image) ? Storage::disk('public')->url('suppliers/'.e($supplier->image)) : null, 'url' => e($supplier->url), 'address' => ($supplier->address) ? e($supplier->address) : null, 'address2' => ($supplier->address2) ? e($supplier->address2) : null, diff --git a/app/Models/Accessory.php b/app/Models/Accessory.php index 76b50704c..41dee8ef1 100755 --- a/app/Models/Accessory.php +++ b/app/Models/Accessory.php @@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; use Watson\Validating\ValidatingTrait; use App\Notifications\CheckinAccessoryNotification; use App\Notifications\CheckoutAccessoryNotification; +use Illuminate\Support\Facades\Storage; /** * Model for Accessories. @@ -219,7 +220,7 @@ class Accessory extends SnipeModel */ public function getImageUrl() { if ($this->image) { - return url('/').'/uploads/accessories/'.$this->image; + return Storage::disk('public')->url(app('accessories_upload_path').$this->image); } return false; diff --git a/app/Models/Asset.php b/app/Models/Asset.php index c2d48cee2..e0918d20a 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -21,6 +21,7 @@ use Watson\Validating\ValidatingTrait; use DB; use App\Notifications\CheckinAssetNotification; use App\Notifications\CheckoutAssetNotification; +use Illuminate\Support\Facades\Storage; /** * Model for Assets. * @@ -159,7 +160,32 @@ class Asset extends Depreciable 'model' => ['name', 'model_number'], 'model.category' => ['name'], 'model.manufacturer' => ['name'], - ]; + ]; + + + /** + * This handles the custom field validation for assets + * + * @var array + */ + public function save($params = []) + { + $settings = \App\Models\Setting::getSettings(); + + // I don't remember why we have this here? Asset tag would always be required, even if auto increment is on... + $this->rules['asset_tag'] = ($settings->auto_increment_assets == '1') ? 'max:255' : 'required'; + + if($this->model_id != '') { + $model = AssetModel::find($this->model_id); + + if (($model) && ($model->fieldset)) { + $this->rules += $model->fieldset->validation_rules(); + } + } + + return parent::save($params); + } + public function getDisplayNameAttribute() { @@ -486,9 +512,9 @@ class Asset extends Depreciable public function getImageUrl() { if ($this->image && !empty($this->image)) { - return url('/').'/uploads/assets/'.$this->image; + return Storage::disk('public')->url(app('assets_upload_path').e($this->image)); } elseif ($this->model && !empty($this->model->image)) { - return url('/').'/uploads/models/'.$this->model->image; + return Storage::disk('public')->url(app('models_upload_path').e($this->model->image)); } return false; } diff --git a/app/Models/AssetModel.php b/app/Models/AssetModel.php index a570cd8c7..1998b277d 100755 --- a/app/Models/AssetModel.php +++ b/app/Models/AssetModel.php @@ -8,6 +8,7 @@ use App\Presenters\Presentable; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Watson\Validating\ValidatingTrait; +use Illuminate\Support\Facades\Storage; /** * Model for Asset Models. Asset Models contain higher level @@ -175,7 +176,7 @@ class AssetModel extends SnipeModel */ public function getImageUrl() { if ($this->image) { - return url('/').'/uploads/models/'.$this->image; + return Storage::disk('public')->url(app('models_upload_path').$this->image); } return false; } diff --git a/app/Models/Consumable.php b/app/Models/Consumable.php index 596106c26..853c52493 100644 --- a/app/Models/Consumable.php +++ b/app/Models/Consumable.php @@ -7,6 +7,7 @@ use App\Presenters\Presentable; use Illuminate\Database\Eloquent\SoftDeletes; use Watson\Validating\ValidatingTrait; use App\Notifications\CheckoutConsumableNotification; +use Illuminate\Support\Facades\Storage; class Consumable extends SnipeModel { @@ -203,7 +204,7 @@ class Consumable extends SnipeModel */ public function getImageUrl() { if ($this->image) { - return url('/').'/uploads/consumables/'.$this->image; + return Storage::disk('public')->url(app('consumables_upload_path').$this->image); } return false; diff --git a/app/Presenters/UserPresenter.php b/app/Presenters/UserPresenter.php index 0d4985226..cc6c16968 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,7 +321,7 @@ class UserPresenter extends Presenter { if ($this->avatar) { - return config('app.url').'/uploads/avatars/'.$this->avatar; + return Storage::disk('public')->url('avatars/'.$this->avatar, $this->avatar); } if ((Setting::getSettings()->load_remote=='1') && ($this->email!='')) { diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php index ed965bf9c..63ed2e68e 100644 --- a/app/Providers/SettingsServiceProvider.php +++ b/app/Providers/SettingsServiceProvider.php @@ -39,76 +39,85 @@ class SettingsServiceProvider extends ServiceProvider * Set some common variables so that they're globally available. * The paths should always be public (versus private uploads) */ + + + // Model paths and URLs + + \App::singleton('assets_upload_path', function(){ + return 'assets/'; + }); + + \App::singleton('models_upload_path', function(){ - return public_path('/uploads/models/'); + return 'assetmodels/'; }); \App::singleton('models_upload_url', function(){ - return url('/').'/uploads/models/'; + return 'assetmodels/'; }); // Categories \App::singleton('categories_upload_path', function(){ - return public_path('/uploads/categories/'); + return 'categories/'; }); \App::singleton('categories_upload_url', function(){ - return url('/').'/uploads/categories/'; + return 'categories/'; }); // Locations \App::singleton('locations_upload_path', function(){ - return public_path('/uploads/locations/'); + return 'locations/'; }); \App::singleton('locations_upload_url', function(){ - return url('/').'/uploads/locations/'; + return 'storage/public_uploads/locations/'; }); // Users \App::singleton('users_upload_path', function(){ - return public_path('/uploads/users/'); + return 'users/'; }); \App::singleton('users_upload_url', function(){ - return url('/').'/uploads/users/'; + return 'public_uploads/users/'; }); // Manufacturers \App::singleton('manufacturers_upload_path', function(){ - return public_path('/uploads/manufacturers/'); + return 'manufacturers/'; }); \App::singleton('manufacturers_upload_url', function(){ - return url('/').'/uploads/manufacturers/'; + return 'public_uploads/manufacturers/'; }); // Suppliers \App::singleton('suppliers_upload_path', function(){ - return public_path('/uploads/suppliers/'); + return 'suppliers/'; }); \App::singleton('suppliers_upload_url', function(){ - return url('/').'/uploads/suppliers/'; + return 'storage/public_uploads/suppliers/'; }); // Departments \App::singleton('departments_upload_path', function(){ - return public_path('/uploads/departments/'); + return 'departments/'; }); \App::singleton('departments_upload_url', function(){ - return url('/').'/uploads/departments/'; + return 'departments/'; }); // Company paths and URLs \App::singleton('companies_upload_path', function(){ - return public_path('/uploads/companies/'); + return 'companies/'; }); \App::singleton('companies_upload_url', function(){ - return url('/').'/uploads/companies/'; + return 'storage/public_uploads/companies/'; }); diff --git a/composer.json b/composer.json index 41e35cdf9..d92704077 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ "league/csv": "^9.0", "league/flysystem-aws-s3-v3": "~1.0", "league/flysystem-cached-adapter": "~1.0", + "league/flysystem-rackspace": "^1.0", "league/flysystem-sftp": "~1.0", "maknz/slack": "^1.7", "neitanod/forceutf8": "^2.0", diff --git a/composer.lock b/composer.lock index 027871bbd..d18eed45c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "94a7bad067cd5d3ea24175dce2f318d6", + "content-hash": "1b2d866e20b5d160ea9d055221d1fec4", "packages": [ { "name": "aws/aws-sdk-php", - "version": "3.67.7", + "version": "3.67.22", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "002577f67703af64a1be35d65edd6a74842c1e65" + "reference": "0d05816beeaf187a3897c28aaa68d683974818d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/002577f67703af64a1be35d65edd6a74842c1e65", - "reference": "002577f67703af64a1be35d65edd6a74842c1e65", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/0d05816beeaf187a3897c28aaa68d683974818d9", + "reference": "0d05816beeaf187a3897c28aaa68d683974818d9", "shasum": "" }, "require": { @@ -84,7 +84,7 @@ "s3", "sdk" ], - "time": "2018-09-06T22:05:51+00:00" + "time": "2018-09-28T18:46:40+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1108,16 +1108,16 @@ }, { "name": "egulias/email-validator", - "version": "2.1.5", + "version": "2.1.6", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "54859fabea8b3beecbb1a282888d5c990036b9e3" + "reference": "0578b32b30b22de3e8664f797cf846fc9246f786" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/54859fabea8b3beecbb1a282888d5c990036b9e3", - "reference": "54859fabea8b3beecbb1a282888d5c990036b9e3", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/0578b32b30b22de3e8664f797cf846fc9246f786", + "reference": "0578b32b30b22de3e8664f797cf846fc9246f786", "shasum": "" }, "require": { @@ -1161,7 +1161,7 @@ "validation", "validator" ], - "time": "2018-08-16T20:49:45+00:00" + "time": "2018-09-25T20:47:26+00:00" }, { "name": "erusev/parsedown", @@ -1309,6 +1309,99 @@ "homepage": "https://github.com/firebase/php-jwt", "time": "2017-06-27T22:17:23+00:00" }, + { + "name": "guzzle/guzzle", + "version": "v3.8.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": ">=2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "*", + "monolog/monolog": "1.*", + "phpunit/phpunit": "3.7.*", + "psr/log": "1.0.*", + "symfony/class-loader": "*", + "zendframework/zend-cache": "<2.3", + "zendframework/zend-log": "<2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.8-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "abandoned": "guzzlehttp/guzzle", + "time": "2014-01-28T22:29:15+00:00" + }, { "name": "guzzlehttp/guzzle", "version": "6.3.3", @@ -1562,32 +1655,32 @@ }, { "name": "jakub-onderka/php-console-color", - "version": "0.1", + "version": "v0.2", "source": { "type": "git", "url": "https://github.com/JakubOnderka/PHP-Console-Color.git", - "reference": "e0b393dacf7703fc36a4efc3df1435485197e6c1" + "reference": "d5deaecff52a0d61ccb613bb3804088da0307191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/e0b393dacf7703fc36a4efc3df1435485197e6c1", - "reference": "e0b393dacf7703fc36a4efc3df1435485197e6c1", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/d5deaecff52a0d61ccb613bb3804088da0307191", + "reference": "d5deaecff52a0d61ccb613bb3804088da0307191", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": ">=5.4.0" }, "require-dev": { "jakub-onderka/php-code-style": "1.0", - "jakub-onderka/php-parallel-lint": "0.*", + "jakub-onderka/php-parallel-lint": "1.0", "jakub-onderka/php-var-dump-check": "0.*", - "phpunit/phpunit": "3.7.*", + "phpunit/phpunit": "~4.3", "squizlabs/php_codesniffer": "1.*" }, "type": "library", "autoload": { - "psr-0": { - "JakubOnderka\\PhpConsoleColor": "src/" + "psr-4": { + "JakubOnderka\\PhpConsoleColor\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1597,11 +1690,10 @@ "authors": [ { "name": "Jakub Onderka", - "email": "jakub.onderka@gmail.com", - "homepage": "http://www.acci.cz" + "email": "jakub.onderka@gmail.com" } ], - "time": "2014-04-08T15:00:19+00:00" + "time": "2018-09-29T17:23:10+00:00" }, { "name": "jakub-onderka/php-console-highlighter", @@ -1679,16 +1771,16 @@ }, { "name": "laravel/framework", - "version": "v5.7.2", + "version": "v5.7.6", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "86e1f98a6d2aab018e0257a7cb2ef2110d64a873" + "reference": "93e761bb5367166ce98ba908d5eb0edd6be76792" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/86e1f98a6d2aab018e0257a7cb2ef2110d64a873", - "reference": "86e1f98a6d2aab018e0257a7cb2ef2110d64a873", + "url": "https://api.github.com/repos/laravel/framework/zipball/93e761bb5367166ce98ba908d5eb0edd6be76792", + "reference": "93e761bb5367166ce98ba908d5eb0edd6be76792", "shasum": "" }, "require": { @@ -1816,7 +1908,7 @@ "framework", "laravel" ], - "time": "2018-09-06T14:01:05+00:00" + "time": "2018-09-25T14:29:00+00:00" }, { "name": "laravel/passport", @@ -2195,26 +2287,26 @@ }, { "name": "league/flysystem", - "version": "1.0.46", + "version": "1.0.47", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "f3e0d925c18b92cf3ce84ea5cc58d62a1762a2b2" + "reference": "a11e4a75f256bdacf99d20780ce42d3b8272975c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f3e0d925c18b92cf3ce84ea5cc58d62a1762a2b2", - "reference": "f3e0d925c18b92cf3ce84ea5cc58d62a1762a2b2", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a11e4a75f256bdacf99d20780ce42d3b8272975c", + "reference": "a11e4a75f256bdacf99d20780ce42d3b8272975c", "shasum": "" }, "require": { + "ext-fileinfo": "*", "php": ">=5.5.9" }, "conflict": { "league/flysystem-sftp": "<1.0.6" }, "require-dev": { - "ext-fileinfo": "*", "phpspec/phpspec": "^3.4", "phpunit/phpunit": "^5.7.10" }, @@ -2275,20 +2367,20 @@ "sftp", "storage" ], - "time": "2018-08-22T07:45:22+00:00" + "time": "2018-09-14T15:30:29+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "1.0.19", + "version": "1.0.20", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "f135691ef6761542af301b7c9880f140fb12dc74" + "reference": "398c56027e49653712a8fba1eb12600d2a83f3b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/f135691ef6761542af301b7c9880f140fb12dc74", - "reference": "f135691ef6761542af301b7c9880f140fb12dc74", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/398c56027e49653712a8fba1eb12600d2a83f3b7", + "reference": "398c56027e49653712a8fba1eb12600d2a83f3b7", "shasum": "" }, "require": { @@ -2322,7 +2414,7 @@ } ], "description": "Flysystem adapter for the AWS S3 SDK v3.x", - "time": "2018-03-27T20:33:59+00:00" + "time": "2018-09-25T12:02:44+00:00" }, { "name": "league/flysystem-cached-adapter", @@ -2371,6 +2463,53 @@ "description": "An adapter decorator to enable meta-data caching.", "time": "2018-07-09T20:51:04+00:00" }, + { + "name": "league/flysystem-rackspace", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-rackspace.git", + "reference": "ba877e837f5dce60e78a0555de37eb9bfc7dd6b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-rackspace/zipball/ba877e837f5dce60e78a0555de37eb9bfc7dd6b9", + "reference": "ba877e837f5dce60e78a0555de37eb9bfc7dd6b9", + "shasum": "" + }, + "require": { + "league/flysystem": "~1.0", + "php": ">=5.4.0", + "rackspace/php-opencloud": "~1.16" + }, + "require-dev": { + "mockery/mockery": "0.9.*", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\Rackspace\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Flysystem adapter for Rackspace", + "time": "2016-03-11T12:13:42+00:00" + }, { "name": "league/flysystem-sftp", "version": "1.0.16", @@ -2533,6 +2672,34 @@ "time": "2015-06-03T03:35:16+00:00" }, { + "name": "mikemccabe/json-patch-php", + "version": "0.1.0", + "source": { + "type": "git", + "url": "https://github.com/mikemccabe/json-patch-php.git", + "reference": "b3af30a6aec7f6467c773cd49b2d974a70f7c0d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mikemccabe/json-patch-php/zipball/b3af30a6aec7f6467c773cd49b2d974a70f7c0d4", + "reference": "b3af30a6aec7f6467c773cd49b2d974a70f7c0d4", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "mikemccabe\\JsonPatch\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "description": "Produce and apply json-patch objects", + "time": "2015-01-05T21:19:54+00:00" + }, + { + "name": "monolog/monolog", "version": "1.23.0", "source": { @@ -2701,16 +2868,16 @@ }, { "name": "nesbot/carbon", - "version": "1.33.0", + "version": "1.34.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "55667c1007a99e82030874b1bb14d24d07108413" + "reference": "1dbd3cb01c5645f3e7deda7aa46ef780d95fcc33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/55667c1007a99e82030874b1bb14d24d07108413", - "reference": "55667c1007a99e82030874b1bb14d24d07108413", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/1dbd3cb01c5645f3e7deda7aa46ef780d95fcc33", + "reference": "1dbd3cb01c5645f3e7deda7aa46ef780d95fcc33", "shasum": "" }, "require": { @@ -2752,20 +2919,20 @@ "datetime", "time" ], - "time": "2018-08-07T08:39:47+00:00" + "time": "2018-09-20T19:36:25+00:00" }, { "name": "nikic/php-parser", - "version": "v4.0.3", + "version": "v4.0.4", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "bd088dc940a418f09cda079a9b5c7c478890fb8d" + "reference": "fa6ee28600d21d49b2b4e1006b48426cec8e579c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bd088dc940a418f09cda079a9b5c7c478890fb8d", - "reference": "bd088dc940a418f09cda079a9b5c7c478890fb8d", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/fa6ee28600d21d49b2b4e1006b48426cec8e579c", + "reference": "fa6ee28600d21d49b2b4e1006b48426cec8e579c", "shasum": "" }, "require": { @@ -2803,7 +2970,7 @@ "parser", "php" ], - "time": "2018-07-15T17:25:16+00:00" + "time": "2018-09-18T07:03:24+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -3768,6 +3935,63 @@ ], "time": "2018-09-05T11:40:09+00:00" }, + { + "name": "rackspace/php-opencloud", + "version": "v1.16.0", + "source": { + "type": "git", + "url": "https://github.com/rackspace/php-opencloud.git", + "reference": "d6b71feed7f9e7a4b52e0240a79f06473ba69c8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rackspace/php-opencloud/zipball/d6b71feed7f9e7a4b52e0240a79f06473ba69c8c", + "reference": "d6b71feed7f9e7a4b52e0240a79f06473ba69c8c", + "shasum": "" + }, + "require": { + "guzzle/guzzle": "~3.8", + "mikemccabe/json-patch-php": "~0.1", + "php": ">=5.4", + "psr/log": "~1.0" + }, + "require-dev": { + "apigen/apigen": "~4.0", + "fabpot/php-cs-fixer": "1.0.*@dev", + "jakub-onderka/php-parallel-lint": "0.*", + "phpspec/prophecy": "~1.4", + "phpunit/phpunit": "4.3.*", + "satooshi/php-coveralls": "0.6.*@dev" + }, + "type": "library", + "autoload": { + "psr-0": { + "OpenCloud": [ + "lib/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Jamie Hannaford", + "email": "jamie.hannaford@rackspace.com", + "homepage": "https://github.com/jamiehannaford" + } + ], + "description": "PHP SDK for Rackspace/OpenStack APIs", + "keywords": [ + "Openstack", + "nova", + "opencloud", + "rackspace", + "swift" + ], + "time": "2016-01-29T10:34:57+00:00" + }, { "name": "ramsey/uuid", "version": "3.8.0", @@ -4277,16 +4501,16 @@ }, { "name": "spatie/db-dumper", - "version": "2.10.1", + "version": "2.11.1", "source": { "type": "git", "url": "https://github.com/spatie/db-dumper.git", - "reference": "1192bb0df9f49ee1f0bdecb7aa921d2647b4c7c7" + "reference": "858ea38e341e2e5f74a5b59cc8184c5c7e94b50d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/db-dumper/zipball/1192bb0df9f49ee1f0bdecb7aa921d2647b4c7c7", - "reference": "1192bb0df9f49ee1f0bdecb7aa921d2647b4c7c7", + "url": "https://api.github.com/repos/spatie/db-dumper/zipball/858ea38e341e2e5f74a5b59cc8184c5c7e94b50d", + "reference": "858ea38e341e2e5f74a5b59cc8184c5c7e94b50d", "shasum": "" }, "require": { @@ -4323,7 +4547,7 @@ "mysqldump", "spatie" ], - "time": "2018-08-30T12:10:16+00:00" + "time": "2018-09-27T06:30:34+00:00" }, { "name": "spatie/laravel-backup", @@ -4441,16 +4665,16 @@ }, { "name": "swiftmailer/swiftmailer", - "version": "v6.1.2", + "version": "v6.1.3", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "7d760881d266d63c5e7a1155cbcf2ac656a31ca8" + "reference": "8ddcb66ac10c392d3beb54829eef8ac1438595f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/7d760881d266d63c5e7a1155cbcf2ac656a31ca8", - "reference": "7d760881d266d63c5e7a1155cbcf2ac656a31ca8", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/8ddcb66ac10c392d3beb54829eef8ac1438595f4", + "reference": "8ddcb66ac10c392d3beb54829eef8ac1438595f4", "shasum": "" }, "require": { @@ -4496,7 +4720,7 @@ "mail", "mailer" ], - "time": "2018-07-13T07:04:35+00:00" + "time": "2018-09-11T07:12:52+00:00" }, { "name": "symfony/console", @@ -6195,22 +6419,23 @@ }, { "name": "codeception/codeception", - "version": "2.4.5", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/Codeception/Codeception.git", - "reference": "5fee32d5c82791548931cbc34806b4de6aa1abfc" + "reference": "dee493561daf644134c95cf176fd2c25aff59ea9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Codeception/zipball/5fee32d5c82791548931cbc34806b4de6aa1abfc", - "reference": "5fee32d5c82791548931cbc34806b4de6aa1abfc", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/dee493561daf644134c95cf176fd2c25aff59ea9", + "reference": "dee493561daf644134c95cf176fd2c25aff59ea9", "shasum": "" }, "require": { "behat/gherkin": "^4.4.0", "codeception/phpunit-wrapper": "^6.0.9|^7.0.6", "codeception/stub": "^2.0", + "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", "facebook/webdriver": ">=1.1.3 <2.0", @@ -6258,7 +6483,7 @@ }, "autoload": { "psr-4": { - "Codeception\\": "src\\Codeception", + "Codeception\\": "src/Codeception", "Codeception\\Extension\\": "ext" } }, @@ -6282,7 +6507,7 @@ "functional testing", "unit testing" ], - "time": "2018-08-01T07:21:49+00:00" + "time": "2018-09-24T09:33:01+00:00" }, { "name": "codeception/phpunit-wrapper", @@ -6419,16 +6644,16 @@ }, { "name": "filp/whoops", - "version": "2.2.0", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "181c4502d8f34db7aed7bfe88d4f87875b8e947a" + "reference": "e79cd403fb77fc8963a99ecc30e80ddd885b3311" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/181c4502d8f34db7aed7bfe88d4f87875b8e947a", - "reference": "181c4502d8f34db7aed7bfe88d4f87875b8e947a", + "url": "https://api.github.com/repos/filp/whoops/zipball/e79cd403fb77fc8963a99ecc30e80ddd885b3311", + "reference": "e79cd403fb77fc8963a99ecc30e80ddd885b3311", "shasum": "" }, "require": { @@ -6447,7 +6672,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "2.2-dev" } }, "autoload": { @@ -6476,7 +6701,7 @@ "throwable", "whoops" ], - "time": "2018-03-03T17:56:25+00:00" + "time": "2018-06-30T13:14:06+00:00" }, { "name": "fzaninotto/faker", @@ -7127,6 +7352,17 @@ { "name": "roave/security-advisories", "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Roave/SecurityAdvisories.git", + "reference": "8605f2e74f558d3e349b219e58414b75b0adc30e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/8605f2e74f558d3e349b219e58414b75b0adc30e", + "reference": "8605f2e74f558d3e349b219e58414b75b0adc30e", + "shasum": "" + }, "conflict": { "3f/pygmentize": "<1.2", "adodb/adodb-php": "<5.20.12", @@ -7134,6 +7370,7 @@ "amphp/http": "<1.0.1", "asymmetricrypt/asymmetricrypt": ">=0,<9.9.99", "aws/aws-sdk-php": ">=3,<3.2.1", + "brightlocal/phpwhois": "<=4.2.5", "bugsnag/bugsnag-laravel": ">=2,<2.0.2", "cakephp/cakephp": ">=1.3,<1.3.18|>=2,<2.4.99|>=2.5,<2.5.99|>=2.6,<2.6.12|>=2.7,<2.7.6|>=3,<3.0.15|>=3.1,<3.1.4|>=3.4,<3.4.14|>=3.5,<3.5.17|>=3.6,<3.6.4", "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", @@ -7145,6 +7382,7 @@ "contao/core-bundle": ">=4,<4.4.18|>=4.5,<4.5.8", "contao/listing-bundle": ">=4,<4.4.8", "contao/newsletter-bundle": ">=4,<4.1", + "david-garcia/phpwhois": "<=4.3.1", "doctrine/annotations": ">=1,<1.2.7", "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", "doctrine/common": ">=2,<2.4.3|>=2.5,<2.5.1", @@ -7159,6 +7397,7 @@ "drupal/drupal": ">=7,<7.59|>=8,<8.4.8|>=8.5,<8.5.3", "erusev/parsedown": "<1.7", "ezsystems/ezpublish-legacy": ">=5.3,<5.3.12.3|>=5.4,<5.4.11.3|>=2017.8,<2017.8.1.1|>=2017.12,<2017.12.2.1", + "ezyang/htmlpurifier": "<4.1.1", "firebase/php-jwt": "<2", "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", @@ -7170,7 +7409,11 @@ "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.42|>=5.6,<5.6.30", "illuminate/database": ">=4,<4.0.99|>=4.1,<4.1.29", "illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15", + "ivankristianto/phpwhois": "<=4.3", + "james-heinrich/getid3": "<1.9.9", "joomla/session": "<1.3.1", + "jsmitty12/phpwhois": "<5.1", + "kazist/phpwhois": "<=4.2.6", "kreait/firebase-php": ">=3.2,<3.8.1", "laravel/framework": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.42|>=5.6,<5.6.30", "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", @@ -7180,6 +7423,7 @@ "monolog/monolog": ">=1.8,<1.12", "namshi/jose": "<2.2", "onelogin/php-saml": "<2.10.4", + "openid/php-openid": "<2.3", "oro/crm": ">=1.7,<1.7.4", "oro/platform": ">=1.7,<1.7.4", "padraic/humbug_get_contents": "<1.1.2", @@ -7188,21 +7432,25 @@ "paypal/merchant-sdk-php": "<3.12", "phpmailer/phpmailer": ">=5,<5.2.24", "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3", + "phpwhois/phpwhois": "<=4.2.5", "phpxmlrpc/extras": "<0.6.1", "propel/propel": ">=2.0.0-alpha1,<=2.0.0-alpha7", "propel/propel1": ">=1,<=1.7.1", "pusher/pusher-php-server": "<2.2.1", "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9", "sensiolabs/connect": "<4.2.3", + "serluck/phpwhois": "<=4.2.6", "shopware/shopware": "<5.3.7", "silverstripe/cms": ">=3,<=3.0.11|>=3.1,<3.1.11", "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", "silverstripe/framework": ">=3,<3.3", "silverstripe/userforms": "<3", + "simple-updates/phpwhois": "<=1", "simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4", "simplesamlphp/simplesamlphp": "<1.15.2", "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", "slim/slim": "<2.6", + "smarty/smarty": "<3.1.33", "socalnick/scn-social-auth": "<1.15.2", "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", "stormpath/sdk": ">=0,<9.9.99", @@ -7229,8 +7477,10 @@ "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", "thelia/backoffice-default-template": ">=2.1,<2.1.2", - "thelia/thelia": ">=2.1,<2.1.2|>=2.1.0-beta1,<2.1.3", + "thelia/thelia": ">=2.1.0-beta1,<2.1.3|>=2.1,<2.1.2", + "theonedemon/phpwhois": "<=4.2.5", "titon/framework": ">=0,<9.9.99", + "truckersmp/phpwhois": "<=4.3.1", "twig/twig": "<1.20", "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.30|>=8,<8.7.17|>=9,<9.3.2", "typo3/cms-core": ">=8,<8.7.17|>=9,<9.3.2", @@ -7283,7 +7533,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2018-08-14T15:39:17+00:00" + "time": "2018-09-17T20:20:31+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -7610,16 +7860,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.3.1", + "version": "3.3.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "628a481780561150481a9ec74709092b9759b3ec" + "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/628a481780561150481a9ec74709092b9759b3ec", - "reference": "628a481780561150481a9ec74709092b9759b3ec", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/6ad28354c04b364c3c71a34e4a18b629cc3b231e", + "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e", "shasum": "" }, "require": { @@ -7657,7 +7907,7 @@ "phpcs", "standards" ], - "time": "2018-07-26T23:47:18+00:00" + "time": "2018-09-23T23:08:17+00:00" }, { "name": "symfony/browser-kit", diff --git a/config/backup.php b/config/backup.php index bc45801f4..898f467ff 100644 --- a/config/backup.php +++ b/config/backup.php @@ -14,8 +14,7 @@ return [ 'backup' => [ /* - * The name of this application. You can use this name to monitor - * the backups. + * I don't know why they call it name - it's used in the path for uploads */ 'name' => 'backups', @@ -27,8 +26,6 @@ return [ * The list of directories and files that will be included in the backup. */ 'include' => [ - public_path('uploads'), - storage_path('private_uploads'), storage_path('oauth-private.key'), storage_path('oauth-public.key'), (env('BACKUP_ENV')=='true') ? base_path('.env') : base_path('.env.example'), @@ -76,7 +73,7 @@ return [ * The disk names on which the backups will be stored. */ 'disks' => [ - 'local', + env('FILESYSTEM_DISK'), ], ], ], diff --git a/config/filesystems.php b/config/filesystems.php index 6fcf1386b..dd64eda58 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -1,15 +1,6 @@ <?php -/* - |-------------------------------------------------------------------------- - | DO NOT EDIT THIS FILE DIRECTLY. - |-------------------------------------------------------------------------- - | This file reads from your .env configuration file and should not - | be modified directly. -*/ - - -return [ +$config = [ /* |-------------------------------------------------------------------------- @@ -17,10 +8,8 @@ return [ |-------------------------------------------------------------------------- | | Here you may specify the default filesystem disk that should be used - | by the framework. A "local" driver, as well as a variety of cloud - | based drivers are available for your choosing. Just store away! - | - | Supported: "local", "ftp", "s3", "rackspace" + | by the framework. The "local" disk, as well as a variety of cloud + | based disks are available to your application. Just store away! | */ @@ -37,7 +26,7 @@ return [ | */ - 'cloud' => 's3', + 'cloud' => env('FILESYSTEM_CLOUD', 's3'), /* |-------------------------------------------------------------------------- @@ -48,52 +37,54 @@ return [ | may even configure multiple disks of the same driver. Defaults have | been setup for each driver as an example of the required options. | + | Supported Drivers: "local", "ftp", "sftp", "s3", "rackspace" + | */ 'disks' => [ 'local' => [ 'driver' => 'local', - 'root' => base_path(), - ], - - 'ftp' => [ - 'driver' => 'ftp', - 'host' => env('FTP_HOST', 'ftp.yourhost.com'), - 'username' => env('FTP_USERNAME', 'ftp-user'), - 'password' => env('FTP_PASSWORD', 'ftp-pass'), - 'port' => env('FTP_PORT', '21'), - 'root' => env('FTP_ROOT', ''), - 'passive' => env('FTP_PASSIVE', true), - 'ssl' => env('FTP_SSL', true), - 'timeout' => env('FTP_TIMEOUT', 30), + 'root' => storage_path('app'), ], - 'sftp' => [ - 'driver' => 'sftp', - 'host' => env('SFTP_HOST', 'sftp.yourhost.com'), - 'username' => env('SFTP_USERNAME', 'sftp-user'), - 'password' => env('SFTP_PASSWORD', 'sftp-pass'), - 'port' => env('SFTP_PORT', '22'), - 'root' => env('SFTP_ROOT', ''), - 'timeout' => env('SFTP_TIMEOUT', 30), + // This applies the LOCAL public only, not S3/FTP/etc + 'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => env('APP_URL').'/storage', + 'visibility' => 'public', ], 's3' => [ 'driver' => 's3', - 'key' => env('AWS_KEY', null), - 'secret' => env('AWS_SECRET', null), - 'region' => env('AWS_REGION', null), - 'bucket' => env('AWS_BUCKET', null), - - 'cache' => [ - 'store' => env('AWS_CACHE_STORE', 'memcached'), - 'expire' => env('AWS_CACHE_EXPIRES', 600), - 'prefix' => env('AWS_CACHE_PREFIX', 'cache-prefix'), - ], + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION'), + 'bucket' => env('AWS_BUCKET'), + 'url' => env('AWS_URL'), + 'root' => env('AWS_BUCKET_ROOT'), ], + 'rackspace' => [ + 'driver' => 'rackspace', + 'username' => env('RACKSPACE_USERNAME'), + 'key' => env('RACKSPACE_KEY'), + 'container' => env('RACKSPACE_CONTAINER'), + 'endpoint' => 'https://identity.api.rackspacecloud.com/v2.0/', + 'region' => env('RACKSPACE_REGION'), + 'url_type' => env('RACKSPACE_URL_TYPE'), + ], ], ]; + +// When you're dealing with local file storage, the paths will be different than S3 +if (env('FILESYSTEM_DISK')!='local') +{ + $config['disks']['public'] = $config['disks'][env('FILESYSTEM_DISK')]; + $config['disks']['public']['visibility'] = 'public'; +} + +return $config; diff --git a/database/seeds/AssetModelSeeder.php b/database/seeds/AssetModelSeeder.php index fdb1adef0..03c7fb509 100755 --- a/database/seeds/AssetModelSeeder.php +++ b/database/seeds/AssetModelSeeder.php @@ -2,6 +2,7 @@ use Illuminate\Database\Seeder; use App\Models\AssetModel; +use Illuminate\Support\Facades\Storage; class AssetModelSeeder extends Seeder { @@ -39,21 +40,30 @@ class AssetModelSeeder extends Seeder factory(AssetModel::class, 1)->states('ultrafine')->create(); // 17 factory(AssetModel::class, 1)->states('ultrasharp')->create(); // 18 - $src = public_path('/img/demo/models'); - $dst = public_path('/uploads/models'); - - $del_files = glob($dst."/*.*"); + $src = public_path('/img/demo/models/'); + $dst = 'assetmodels'.'/'; + $del_files = Storage::files($dst); foreach($del_files as $del_file){ // iterate files - if(is_file($del_file)) - unlink($del_file); // delete file + $file_to_delete = str_replace($src,'',$del_file); + \Log::debug('Deleting: '.$file_to_delete); + try { + Storage::disk('public')->delete($dst.$del_file); + } catch (\Exception $e) { + \Log::debug($e); + } } $add_files = glob($src."/*.*"); foreach($add_files as $add_file){ - $file_to_copy = str_replace($src,$dst,$add_file); - copy($add_file, $file_to_copy); + $file_to_copy = str_replace($src,'',$add_file); + \Log::debug('Copying: '.$file_to_copy); + try { + Storage::disk('public')->put($dst.$file_to_copy, file_get_contents($src.$file_to_copy)); + } catch (\Exception $e) { + \Log::debug($e); + } } diff --git a/database/seeds/AssetSeeder.php b/database/seeds/AssetSeeder.php index 79e47d611..f9c2f1fc8 100644 --- a/database/seeds/AssetSeeder.php +++ b/database/seeds/AssetSeeder.php @@ -1,7 +1,7 @@ <?php use Illuminate\Database\Seeder; use App\Models\Asset; - +use Illuminate\Support\Facades\Storage; class AssetSeeder extends Seeder { @@ -35,13 +35,14 @@ class AssetSeeder extends Seeder factory(Asset::class, 10)->states('ultrasharp')->create(); - $dst = public_path('/uploads/assets'); - - $del_files = glob($dst."/*.*"); - + $del_files = Storage::files('companies'); foreach($del_files as $del_file){ // iterate files - if(is_file($del_file)) - unlink($del_file); // delete file + \Log::debug('Deleting: '.$del_files); + try { + Storage::disk('public')->delete('assets'.'/'.$del_files); + } catch (\Exception $e) { + \Log::debug($e); + } } DB::table('checkout_requests')->truncate(); diff --git a/database/seeds/CompanySeeder.php b/database/seeds/CompanySeeder.php index 3885b939f..eeae5492b 100644 --- a/database/seeds/CompanySeeder.php +++ b/database/seeds/CompanySeeder.php @@ -12,26 +12,37 @@ class CompanySeeder extends Seeder */ public function run() { - // + \Log::debug('Seed companies'); Company::truncate(); factory(Company::class, 4)->create(); - $src = public_path('/img/demo/companies'); - $dst = public_path('/uploads/companies'); - $del_files = glob($dst."/*.*"); + $src = public_path('/img/demo/companies/'); + $dst = 'companies'.'/'; + $del_files = Storage::files('companies/'.$dst); foreach($del_files as $del_file){ // iterate files - if(is_file($del_file)) - unlink($del_file); // delete file + $file_to_delete = str_replace($src,'',$del_file); + \Log::debug('Deleting: '.$file_to_delete); + try { + Storage::disk('public')->delete($dst.$del_file); + } catch (\Exception $e) { + \Log::debug($e); + } } $add_files = glob($src."/*.*"); foreach($add_files as $add_file){ - $file_to_copy = str_replace($src,$dst,$add_file); - copy($add_file, $file_to_copy); + $file_to_copy = str_replace($src,'',$add_file); + \Log::debug('Copying: '.$file_to_copy); + try { + Storage::disk('public')->put($dst.$file_to_copy, file_get_contents($src.$file_to_copy)); + } catch (\Exception $e) { + \Log::debug($e); + } } + } } diff --git a/database/seeds/LocationSeeder.php b/database/seeds/LocationSeeder.php index 40d389792..f7d857f34 100644 --- a/database/seeds/LocationSeeder.php +++ b/database/seeds/LocationSeeder.php @@ -1,7 +1,7 @@ <?php use Illuminate\Database\Seeder; use App\Models\Location; - +use Illuminate\Support\Facades\Storage; class LocationSeeder extends Seeder { @@ -10,21 +10,30 @@ class LocationSeeder extends Seeder Location::truncate(); factory(Location::class, 10)->create(); - $src = public_path('/img/demo/locations'); - $dst = public_path('/uploads/locations'); - - $del_files = glob($dst."/*.*"); + $src = public_path('/img/demo/locations/'); + $dst = 'locations'.'/'; + $del_files = Storage::files($dst); foreach($del_files as $del_file){ // iterate files - if(is_file($del_file)) - unlink($del_file); // delete file + $file_to_delete = str_replace($src,'',$del_file); + \Log::debug('Deleting: '.$file_to_delete); + try { + Storage::disk('public')->delete($dst.$del_file); + } catch (\Exception $e) { + \Log::debug($e); + } } $add_files = glob($src."/*.*"); foreach($add_files as $add_file){ - $file_to_copy = str_replace($src,$dst,$add_file); - copy($add_file, $file_to_copy); + $file_to_copy = str_replace($src,'',$add_file); + \Log::debug('Copying: '.$file_to_copy); + try { + Storage::disk('public')->put($dst.$file_to_copy, file_get_contents($src.$file_to_copy)); + } catch (\Exception $e) { + \Log::debug($e); + } } } diff --git a/database/seeds/ManufacturerSeeder.php b/database/seeds/ManufacturerSeeder.php index f6d9a2451..a65d12a2e 100644 --- a/database/seeds/ManufacturerSeeder.php +++ b/database/seeds/ManufacturerSeeder.php @@ -1,6 +1,7 @@ <?php use Illuminate\Database\Seeder; use App\Models\Manufacturer; +use Illuminate\Support\Facades\Storage; class ManufacturerSeeder extends Seeder { @@ -19,21 +20,30 @@ class ManufacturerSeeder extends Seeder factory(Manufacturer::class, 1)->states('avery')->create(); // 10 factory(Manufacturer::class, 1)->states('crucial')->create(); // 10 - $src = public_path('/img/demo/manufacturers'); - $dst = public_path('/uploads/manufacturers'); - - $del_files = glob($dst."/*.*"); + $src = public_path('/img/demo/manufacturers/'); + $dst = 'manufacturers'.'/'; + $del_files = Storage::files($dst); foreach($del_files as $del_file){ // iterate files - if(is_file($del_file)) - unlink($del_file); // delete file + $file_to_delete = str_replace($src,'',$del_file); + \Log::debug('Deleting: '.$file_to_delete); + try { + Storage::disk('public')->delete($dst.$del_file); + } catch (\Exception $e) { + \Log::debug($e); + } } $add_files = glob($src."/*.*"); foreach($add_files as $add_file){ - $file_to_copy = str_replace($src,$dst,$add_file); - copy($add_file, $file_to_copy); + $file_to_copy = str_replace($src,'',$add_file); + \Log::debug('Copying: '.$file_to_copy); + try { + Storage::disk('public')->put($dst.$file_to_copy, file_get_contents($src.$file_to_copy)); + } catch (\Exception $e) { + \Log::debug($e); + } } diff --git a/public/img/demo/logo.png b/public/img/demo/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..423a5b74906c4a1f67dab665a59a6731f0dbaa98 GIT binary patch literal 22482 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 literal 0 HcmV?d00001 diff --git a/resources/lang/en/general.php b/resources/lang/en/general.php index ea339cf9a..69690be01 100644 --- a/resources/lang/en/general.php +++ b/resources/lang/en/general.php @@ -125,6 +125,7 @@ 'feature_disabled' => 'This feature has been disabled for the demo installation.', 'location' => 'Location', 'locations' => 'Locations', + 'logo_size' => 'Square logos look best with Logo + Text. Logo maximum display size is 50px high x 500px wide. ', 'logout' => 'Logout', 'lookup_by_tag' => 'Lookup by Asset Tag', 'maintenances' => 'Maintenances', diff --git a/resources/views/accessories/edit.blade.php b/resources/views/accessories/edit.blade.php index 6b2065a08..9148c8b13 100755 --- a/resources/views/accessories/edit.blade.php +++ b/resources/views/accessories/edit.blade.php @@ -29,7 +29,7 @@ <label class="col-md-3 control-label" for="image_delete">{{ trans('general.image_delete') }}</label> <div class="col-md-5"> {{ Form::checkbox('image_delete') }} - <img src="{{ url('/') }}/uploads/accessories/{{ $item->image }}" /> + <img src="{{ Storage::disk('public')->url(app('accessories_upload_path').e($item->image)) }}" class="img-responsive" /> {!! $errors->first('image_delete', '<span class="alert-msg">:message</span>') !!} </div> </div> diff --git a/resources/views/account/profile.blade.php b/resources/views/account/profile.blade.php index 18a828703..bbff2a602 100755 --- a/resources/views/account/profile.blade.php +++ b/resources/views/account/profile.blade.php @@ -90,7 +90,7 @@ <label class="col-md-3 control-label" for="avatar_delete">{{ trans('general.avatar_delete') }}</label> <div class="col-md-8"> {{ Form::checkbox('avatar_delete') }} - <img src="{{ url('/') }}/uploads/avatars/{{ $user->avatar }}" class="avatar img-circle"> + <img src="{{ $user->present()->gravatar}}" class="avatar img-circle"> {!! $errors->first('avatar_delete', '<span class="alert-msg">:message</span>') !!} </div> </div> diff --git a/resources/views/categories/edit.blade.php b/resources/views/categories/edit.blade.php index 88bc6457b..ca43c49bb 100755 --- a/resources/views/categories/edit.blade.php +++ b/resources/views/categories/edit.blade.php @@ -79,7 +79,7 @@ <label class="col-md-3 control-label" for="image_delete">{{ trans('general.image_delete') }}</label> <div class="col-md-9"> {{ Form::checkbox('image_delete') }} - <img src="{{ url('/') }}/uploads/categories/{{ $item->image }}" /> + <img src="{{ Storage::disk('public')->url(app('categories_upload_path').e($item->image)) }}" class="img-responsive" /> {!! $errors->first('image_delete', '<span class="alert-msg">:message</span>') !!} </div> </div> diff --git a/resources/views/companies/edit.blade.php b/resources/views/companies/edit.blade.php index 8f63b05c4..ee77efc4f 100644 --- a/resources/views/companies/edit.blade.php +++ b/resources/views/companies/edit.blade.php @@ -16,7 +16,7 @@ <label class="col-md-3 control-label" for="image_delete">{{ trans('general.image_delete') }}</label> <div class="col-md-5"> {{ Form::checkbox('image_delete') }} - <img src="{{ url('/') }}/uploads/companies/{{ $item->image }}" /> + <img src="{{ Storage::disk('public')->url(app('companies_upload_path').e($item->image)) }}" class="img-responsive" /> {!! $errors->first('image_delete', '<span class="alert-msg">:message</span>') !!} </div> </div> diff --git a/resources/views/components/edit.blade.php b/resources/views/components/edit.blade.php index 8e9be3738..9bfdf6a43 100644 --- a/resources/views/components/edit.blade.php +++ b/resources/views/components/edit.blade.php @@ -27,7 +27,7 @@ <label class="col-md-3 control-label" for="image_delete">{{ trans('general.image_delete') }}</label> <div class="col-md-5"> {{ Form::checkbox('image_delete') }} - <img src="{{ url('/') }}/uploads/components/{{ $item->image }}" /> + <img src="{{ Storage::disk('public')->url(app('components_upload_path').e($item->image)) }}" class="img-responsive" /> {!! $errors->first('image_delete', '<span class="alert-msg">:message</span>') !!} </div> </div> diff --git a/resources/views/consumables/edit.blade.php b/resources/views/consumables/edit.blade.php index 9496019e4..4392c1f1b 100644 --- a/resources/views/consumables/edit.blade.php +++ b/resources/views/consumables/edit.blade.php @@ -27,7 +27,7 @@ <label class="col-md-3 control-label" for="image_delete">{{ trans('general.image_delete') }}</label> <div class="col-md-5"> {{ Form::checkbox('image_delete') }} - <img src="{{ url('/') }}/uploads/consumables/{{ $item->image }}" /> + <img src="{{ Storage::disk('public')->url(app('consumables_upload_path').e($item->image)) }}" class="img-responsive" /> {!! $errors->first('image_delete', '<span class="alert-msg">:message</span>') !!} </div> </div> diff --git a/resources/views/departments/edit.blade.php b/resources/views/departments/edit.blade.php index ff5bbfd16..4d3952528 100644 --- a/resources/views/departments/edit.blade.php +++ b/resources/views/departments/edit.blade.php @@ -29,7 +29,7 @@ <label class="col-md-3 control-label" for="image_delete">{{ trans('general.image_delete') }}</label> <div class="col-md-5"> {{ Form::checkbox('image_delete') }} - <img src="{{ url('/') }}/uploads/departments/{{ $item->image }}" /> + <img src="{{ Storage::disk('public')->url(app('departments_upload_path').e($item->image)) }}" class="img-responsive" /> {!! $errors->first('image_delete', '<span class="alert-msg">:message</span>') !!} </div> </div> diff --git a/resources/views/hardware/edit.blade.php b/resources/views/hardware/edit.blade.php index c3c5d9be2..708d296ea 100755 --- a/resources/views/hardware/edit.blade.php +++ b/resources/views/hardware/edit.blade.php @@ -86,7 +86,7 @@ {!! $errors->first('image_delete', '<span class="alert-msg">:message</span>') !!} </label> <div style="margin-top: 0.5em"> - <img src="{{ url('/') }}/uploads/assets/{{ $item->image }}" class="img-responsive"/> + <img src="{{ Storage::disk('public')->url(app('assets_upload_path').e($item->image)) }}" class="img-responsive" /> </div> </div> </div> @@ -170,7 +170,7 @@ }); } } - ; + $(function () { //grab custom fields for this model whenever model changes. @@ -184,135 +184,8 @@ user_add($(".status_id").val()); }); - $("#create-form").submit(function (event) { - event.preventDefault(); - return sendForm(); - }); - - // Resize Files when chosen - //First check to see if there is a file before doing anything else - - var imageData = ""; - var $fileInput = $('#uploadFile'); - $fileInput.on('change', function (e) { - if ($fileInput != '') { - if (window.File && window.FileReader && window.FormData) { - var file = e.target.files[0]; - if (file) { - if (/^image\//i.test(file.type)) { - readFile(file); - } else { - alert('Invalid Image File :('); - } - } - } - else { - console.log("File API not supported, not resizing"); - } - } - }); - - - function readFile(file) { - var reader = new FileReader(); - - reader.onloadend = function () { - processFile(reader.result, file.type); - } - - reader.onerror = function () { - alert("Unable to read file"); - } - - reader.readAsDataURL(file); - } - - function processFile(dataURL, fileType) { - var maxWidth = 800; - var maxHeight = 800; - - var image = new Image(); - image.src = dataURL; - - image.onload = function () { - var width = image.width; - var height = image.height; - var shouldResize = (width > maxWidth) || (height > maxHeight); - - if (!shouldResize) { - imageData = dataURL; - return; - } - - var newWidth; - var newHeight; - - if (width > height) { - newHeight = height * (maxWidth / width); - newWidth = maxWidth; - } else { - newWidth = width * (maxHeight / height); - newHeight = maxHeight; - } - var canvas = document.createElement('canvas'); - - canvas.width = newWidth; - canvas.height = newHeight; - - var context = canvas.getContext('2d'); - - context.drawImage(this, 0, 0, newWidth, newHeight); - - dataURL = canvas.toDataURL(fileType); - - imageData = dataURL; - - }; - - image.onerror = function () { - alert('Unable to process file :('); - } - } - - function sendForm() { - var form = $("#create-form").get(0); - var formData = $('#create-form').serializeArray(); - formData.push({name: 'image', value: imageData}); - $.ajax({ - type: 'POST', - url: form.action, - headers: { - "X-Requested-With": 'XMLHttpRequest', - "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content') - }, - data: formData, - dataType: 'json', - success: function (data) { - // console.dir(data); - // AssetController flashes success to session, redirect to hardware page. - if (data.redirect_url) { - window.location.href = data.redirect_url; - return true; - } - window.location.reload(true); - return false; - - }, - error: function (data) { - // AssetRequest Validator will flash all errors to session, this just refreshes to see them. - window.location.reload(true); - // console.log(JSON.stringify(data)); - // console.log('error submitting'); - } - }); - - return false; - } - }); - - </script> @stop diff --git a/resources/views/hardware/view.blade.php b/resources/views/hardware/view.blade.php index dd8ea1b6e..f6b5ffc69 100755 --- a/resources/views/hardware/view.blade.php +++ b/resources/views/hardware/view.blade.php @@ -497,9 +497,9 @@ <div class="col-md-4"> @if ($asset->image) - <img src="{{ url('/') }}/uploads/assets/{{{ $asset->image }}}" class="assetimg img-responsive"> + <img src="{{ Storage::disk('public')->url(app('assets_upload_path').e($asset->image)) }}" class="assetimg img-responsive"> @elseif (($asset->model) && ($asset->model->image!='')) - <img src="{{ url('/') }}/uploads/models/{{{ $asset->model->image }}}" class="assetimg img-responsive"> + <img src="{{ Storage::disk('public')->url(app('models_upload_url').e($asset->model->image )) }}" class="assetimg img-responsive"> @endif @if ($snipeSettings->qr_code=='1') diff --git a/resources/views/layouts/basic.blade.php b/resources/views/layouts/basic.blade.php index 1e9b3334b..4942c2404 100644 --- a/resources/views/layouts/basic.blade.php +++ b/resources/views/layouts/basic.blade.php @@ -45,7 +45,7 @@ @if (($snipeSettings) && ($snipeSettings->logo!='')) <center> - <img id="login-logo" src="{{ url('/') }}/uploads/{{ $snipeSettings->logo }}"> + <img id="login-logo" src="{{ Storage::disk('public')->url('').e($snipeSettings->logo) }}"> </center> @endif <!-- Content --> diff --git a/resources/views/layouts/default.blade.php b/resources/views/layouts/default.blade.php index f74fc9ce6..ca06e2e6f 100644 --- a/resources/views/layouts/default.blade.php +++ b/resources/views/layouts/default.blade.php @@ -96,14 +96,14 @@ @if ($snipeSettings->brand == '3') <a class="logo navbar-brand no-hover" href="{{ url('/') }}"> @if ($snipeSettings->logo!='') - <img class="navbar-brand-img" src="{{ url('/') }}/uploads/{{ $snipeSettings->logo }}"> + <img class="navbar-brand-img" src="{{ Storage::disk('public')->url('').e($snipeSettings->logo) }}"> @endif {{ $snipeSettings->site_name }} </a> @elseif ($snipeSettings->brand == '2') <a class="logo navbar-brand no-hover" href="{{ url('/') }}"> @if ($snipeSettings->logo!='') - <img class="navbar-brand-img" src="{{ url('/') }}/uploads/{{ $snipeSettings->logo }}"> + <img class="navbar-brand-img" src="{{ Storage::disk('public')->url('').e($snipeSettings->logo) }}"> @endif </a> @else diff --git a/resources/views/locations/edit.blade.php b/resources/views/locations/edit.blade.php index 253a069ce..3990c5b8f 100755 --- a/resources/views/locations/edit.blade.php +++ b/resources/views/locations/edit.blade.php @@ -54,9 +54,9 @@ @if ($item->image) <div class="form-group {{ $errors->has('image_delete') ? 'has-error' : '' }}"> <label class="col-md-3 control-label" for="image_delete">{{ trans('general.image_delete') }}</label> - <div class="col-md-5"> + <div class="col-md-9"> {{ Form::checkbox('image_delete') }} - <img src="{{ url('/') }}/uploads/locations/{{ $item->image }}" /> + <img src="{{ Storage::disk('public')->url(app('locations_upload_path').e($item->image)) }}" class="img-responsive" /> {!! $errors->first('image_delete', '<span class="alert-msg">:message</span>') !!} </div> </div> diff --git a/resources/views/manufacturers/edit.blade.php b/resources/views/manufacturers/edit.blade.php index af212be8f..eb8fa88b4 100755 --- a/resources/views/manufacturers/edit.blade.php +++ b/resources/views/manufacturers/edit.blade.php @@ -56,7 +56,7 @@ <label class="col-md-3 control-label" for="image_delete">{{ trans('general.image_delete') }}</label> <div class="col-md-5"> {{ Form::checkbox('image_delete') }} - <img src="{{ url('/') }}/uploads/manufacturers/{{ $item->image }}" /> + <img src="{{ Storage::disk('public')->url(app('manufacturers_upload_path').e($item->image)) }}" class="img-responsive" /> {!! $errors->first('image_delete', '<span class="alert-msg">:message</span>') !!} </div> </div> diff --git a/resources/views/models/edit.blade.php b/resources/views/models/edit.blade.php index ae271c583..ccf8728c8 100755 --- a/resources/views/models/edit.blade.php +++ b/resources/views/models/edit.blade.php @@ -62,7 +62,7 @@ <label class="col-md-3 control-label" for="image_delete">{{ trans('general.image_delete') }}</label> <div class="col-md-5"> {{ Form::checkbox('image_delete') }} - <img src="{{ url('/') }}/uploads/models/{{ $item->image }}" /> + <img src="{{ Storage::disk('public')->url(app('models_upload_path').e($item->image)) }}" class="img-responsive" /> {!! $errors->first('image_delete', '<span class="alert-msg"><br>:message</span>') !!} </div> </div> diff --git a/resources/views/partials/forms/edit/image-upload.blade.php b/resources/views/partials/forms/edit/image-upload.blade.php index 32b1b3ce5..b0c7819b6 100644 --- a/resources/views/partials/forms/edit/image-upload.blade.php +++ b/resources/views/partials/forms/edit/image-upload.blade.php @@ -3,14 +3,14 @@ <div class="col-md-9"> <label class="btn btn-default"> {{ trans('button.select_file') }} - <input type="file" name="image" id="uploadFile" data-maxsize="{{ \App\Helpers\Helper::file_upload_max_size() }}" accept="image/gif,image/jpeg,image/png,image/svg" style="display:none"> + <input type="file" name="image" id="uploadFile" data-maxsize="{{ \App\Helpers\Helper::file_upload_max_size() }}" accept="image/gif,image/jpeg,image/png,image/svg" style="display:none; max-width: 90%"> </label> <span class='label label-default' id="upload-file-info"></span> <p class="help-block" id="upload-file-status">{{ trans('general.image_filetypes_help', ['size' => \App\Helpers\Helper::file_upload_max_size_readable()]) }}</p> {!! $errors->first('image', '<span class="alert-msg">:message</span>') !!} </div> - <div class="col-md-4"> + <div class="col-md-9 col-md-offset-3"> <img id="imagePreview" style="max-width: 200px;"> </div> </div> diff --git a/resources/views/settings/backups.blade.php b/resources/views/settings/backups.blade.php index 534a29100..148c81967 100644 --- a/resources/views/settings/backups.blade.php +++ b/resources/views/settings/backups.blade.php @@ -29,7 +29,7 @@ <tbody> @foreach ($files as $file) <tr> - <td><a href="backups/download/{{ $file['filename'] }}">{{ $file['filename'] }}</a></td> + <td><a href="{{ Storage::url('backups/'.e($file['filename'])) }}">{{ $file['filename'] }}</a></td> <td>{{ date("M d, Y g:i A", $file['modified']) }} </td> <td>{{ $file['filesize'] }}</td> <td> diff --git a/resources/views/settings/branding.blade.php b/resources/views/settings/branding.blade.php index 675657046..4a7170459 100644 --- a/resources/views/settings/branding.blade.php +++ b/resources/views/settings/branding.blade.php @@ -37,7 +37,7 @@ <div class="box-body"> - <div class="col-md-11 col-md-offset-1"> + <div class="col-md-12"> <!-- Site name --> <div class="form-group {{ $errors->has('site_name') ? 'error' : '' }}"> @@ -60,23 +60,31 @@ <!-- Logo --> <div class="form-group {{ $errors->has('image') ? 'has-error' : '' }}"> - <label class="col-md-3 control-label" for="image"> - {{ Form::label('logo', trans('admin/settings/general.logo')) }}</label> + <label class="col-md-3" for="image"> + {{ Form::label('image', trans('admin/settings/general.logo')) }}</label> + <div class="col-md-9"> - @if (config('app.lock_passwords')) - <p class="help-block">{{ trans('general.lock_passwords') }}</p> - @else <label class="btn btn-default"> {{ trans('button.select_file') }} - <input type="file" name="image" accept="image/gif,image/jpeg,image/png,image/svg" hidden> + <input type="file" name="image" id="uploadFile" data-maxsize="{{ \App\Helpers\Helper::file_upload_max_size() }}" accept="image/gif,image/jpeg,image/png,image/svg" style="display:none; max-width: 90%"> </label> + <span class='label label-default' id="upload-file-info"></span> - <p class="help-block" id="upload-file-status">{{ trans('general.image_filetypes_help', ['size' => \App\Helpers\Helper::file_upload_max_size_readable()]) }}</p> - + <p class="help-block" id="upload-file-status">{{ trans('general.image_filetypes_help', ['size' => \App\Helpers\Helper::file_upload_max_size_readable()]) }} {{ trans('general.logo_size') }}</p> {!! $errors->first('image', '<span class="alert-msg">:message</span>') !!} - {{ Form::checkbox('clear_logo', '1', Input::old('clear_logo'),array('class' => 'minimal')) }} Remove - @endif + + </div> + <div class="col-md-9 col-md-offset-3"> + <img id="imagePreview" style="max-width: 500px; max-height: 50px"> </div> + + @if ($setting->logo!='') + <div class="col-md-9 col-md-offset-3"> + + {{ Form::checkbox('clear_logo', '1', Input::old('clear_logo'),array('class' => 'minimal')) }} Remove current image + + </div> + @endif </div> @@ -157,7 +165,7 @@ {{ Form::textarea('custom_css', Input::old('custom_css', $setting->custom_css), array('class' => 'form-control','placeholder' => 'Add your custom CSS')) }} {!! $errors->first('custom_css', '<span class="alert-msg">:message</span>') !!} @endif - <p class="help-block">{{ trans('admin/settings/general.custom_css_help') }}</p> + <p class="help-block">{!! trans('admin/settings/general.custom_css_help') !!}</p> </div> </div> diff --git a/resources/views/users/view.blade.php b/resources/views/users/view.blade.php index 20b9b7fd4..09a572c05 100755 --- a/resources/views/users/view.blade.php +++ b/resources/views/users/view.blade.php @@ -115,11 +115,7 @@ </div> @endif <div class="col-md-2 text-center"> - @if ($user->avatar) - <img src="/uploads/avatars/{{ $user->avatar }}" class="avatar img-thumbnail hidden-print"> - @else - <img src="{{ $user->present()->gravatar() }}" class="avatar img-circle hidden-print"> - @endif + <img src="{{ $user->present()->gravatar }}" class="avatar img-thumbnail hidden-print"> </div> <div class="col-md-8"> diff --git a/resources/views/vendor/mail/html/header.blade.php b/resources/views/vendor/mail/html/header.blade.php index 686ee632f..fe93f44a9 100644 --- a/resources/views/vendor/mail/html/header.blade.php +++ b/resources/views/vendor/mail/html/header.blade.php @@ -4,13 +4,13 @@ @if ($snipeSettings->brand == '3') @if ($snipeSettings->logo!='') - <img class="navbar-brand-img logo" src="{{ url('/') }}/uploads/{{ $snipeSettings->logo }}"alt="{{ $snipeSettings->site_name }}"> + <img class="navbar-brand-img logo" src="{{ Storage::disk('public')->url('').e($snipeSettings->logo) }}"alt="{{ $snipeSettings->site_name }}"> @endif {{ $snipeSettings->site_name }} @elseif ($snipeSettings->brand == '2') @if ($snipeSettings->logo!='') - <img class="navbar-brand-img logo" src="{{ url('/') }}/uploads/{{ $snipeSettings->logo }}" alt="{{ $snipeSettings->site_name }}"> + <img class="navbar-brand-img logo" src="{{ Storage::disk('public')->url('').e($snipeSettings->logo) }}" alt="{{ $snipeSettings->site_name }}"> @endif @else {{ $snipeSettings->site_name }} diff --git a/upgrade.php b/upgrade.php index 030d26207..08eafd6b2 100644 --- a/upgrade.php +++ b/upgrade.php @@ -174,16 +174,71 @@ echo "\n"; echo "--------------------------------------------------------\n"; -echo "Step 9: Taking application out of maintenance mode:\n"; +echo "Step 10: Taking application out of maintenance mode:\n"; echo "--------------------------------------------------------\n\n"; $up = shell_exec('php artisan up'); echo '-- '.$up."\n\n"; + +echo "--------------------------------------------------------\n"; +echo "Step 11: Checking for v5 public storage directories: \n"; +echo "--------------------------------------------------------\n\n"; + + +if ((!file_exists('storage/app/public')) && (!is_dir('storage/app/public'))) { + echo "- No public directory found in storage/app - creating one.\n\n"; + if (!mkdir('storage/app/public', 0777, true)) { + echo "ERROR: Failed to create directory at storage/app/public. You should do this manually.\n\n"; + } + $storage_simlink = shell_exec('php artisan storage:link'); + echo $storage_simlink; + +} else { + echo "- Public storage directory already exists. Skipping...\n\n"; +} + +echo "- Copying files into storage/app/public.\n\n"; +if (rmove('public/uploads','storage/app/public')) { + echo "- Copy successful.\n\n"; +} else { + echo "- Copy failed - you should do this manually by copying the files from public/uploads into the storage/app/public directory.\n\n"; +} + echo "--------------------------------------------------------\n"; echo "FINISHED! Clear your browser cookies and re-login to use :\n"; echo "your upgraded Snipe-IT.\n"; echo "--------------------------------------------------------\n\n"; - +/** + * Recursively move files from one directory to another + * + * @param String $src - Source of files being moved + * @param String $dest - Destination of files being moved + */ +function rmove($src, $dest){ + + // If source is not a directory stop processing + if(!is_dir($src)) return false; + + // If the destination directory does not exist create it + if(!is_dir($dest)) { + if(!mkdir($dest)) { + // If the destination directory could not be created stop processing + return false; + } + } + + // Open the source directory to read in files + $i = new DirectoryIterator($src); + foreach($i as $f) { + if($f->isFile()) { + rename($f->getRealPath(), "$dest/" . $f->getFilename()); + } else if(!$f->isDot() && $f->isDir()) { + rmove($f->getRealPath(), "$dest/$f"); + unlink($f->getRealPath()); + } + } + unlink($src); +} -- GitLab