diff --git a/.github/workflows/tests-mysql.yml b/.github/workflows/tests-mysql.yml index 737a86dca3..310414cda6 100644 --- a/.github/workflows/tests-mysql.yml +++ b/.github/workflows/tests-mysql.yml @@ -76,4 +76,4 @@ jobs: DB_DATABASE: snipeit DB_PORT: ${{ job.services.mysql.ports[3306] }} DB_USERNAME: root - run: php artisan test --parallel + run: php artisan test diff --git a/.github/workflows/tests-postgres.yml b/.github/workflows/tests-postgres.yml index 0c361511b8..ae48277be3 100644 --- a/.github/workflows/tests-postgres.yml +++ b/.github/workflows/tests-postgres.yml @@ -74,4 +74,4 @@ jobs: DB_PORT: ${{ job.services.postgresql.ports[5432] }} DB_USERNAME: snipeit DB_PASSWORD: password - run: php artisan test --parallel + run: php artisan test diff --git a/.github/workflows/tests-sqlite.yml b/.github/workflows/tests-sqlite.yml index 49c7c92d8e..8bf0115169 100644 --- a/.github/workflows/tests-sqlite.yml +++ b/.github/workflows/tests-sqlite.yml @@ -58,4 +58,4 @@ jobs: - name: Execute tests (Unit and Feature tests) via PHPUnit env: DB_CONNECTION: sqlite_testing - run: php artisan test --parallel + run: php artisan test diff --git a/app/Console/Commands/ObjectImportCommand.php b/app/Console/Commands/ObjectImportCommand.php index 8370e7c050..a1202ded89 100644 --- a/app/Console/Commands/ObjectImportCommand.php +++ b/app/Console/Commands/ObjectImportCommand.php @@ -6,6 +6,7 @@ use Illuminate\Console\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Illuminate\Support\Facades\Log; +use Symfony\Component\Console\Helper\ProgressIndicator; ini_set('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); //600 seconds = 10 minutes ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M')); @@ -29,6 +30,11 @@ class ObjectImportCommand extends Command */ protected $description = 'Import Items from CSV'; + /** + * The progress indicator instance. + */ + protected ProgressIndicator $progressIndicator; + /** * Create a new command instance. * @@ -39,8 +45,6 @@ class ObjectImportCommand extends Command parent::__construct(); } - private $bar; - /** * Execute the console command. * @@ -48,6 +52,8 @@ class ObjectImportCommand extends Command */ public function handle() { + $this->progressIndicator = new ProgressIndicator($this->output); + $filename = $this->argument('filename'); $class = title_case($this->option('item-type')); $classString = "App\\Importer\\{$class}Importer"; @@ -61,46 +67,25 @@ class ObjectImportCommand extends Command // This $logFile/useFiles() bit is currently broken, so commenting it out for now // $logFile = $this->option('logfile'); // Log::useFiles($logFile); - $this->comment('======= Importing Items from '.$filename.' ========='); + $this->progressIndicator->start('======= Importing Items from '.$filename.' ========='); + $importer->import(); - $this->bar = null; - - if (! empty($this->errors)) { - $this->comment('The following Errors were encountered.'); - foreach ($this->errors as $asset => $error) { - $this->comment('Error: Item: '.$asset.' failed validation: '.json_encode($error)); - } - } else { - $this->comment('All Items imported successfully!'); - } - $this->comment(''); + $this->progressIndicator->finish('Import finished.'); } - public function errorCallback($item, $field, $errorString) + public function errorCallback($item, $field, $error) { - $this->errors[$item->name][$field] = $errorString; + $this->output->write("\x0D\x1B[2K"); + + $this->warn('Error: Item: '.$item->name.' failed validation: '.json_encode($error)); } - public function progress($count) + public function progress($importedItemsCount) { - if (! $this->bar) { - $this->bar = $this->output->createProgressBar($count); - } - static $index = 0; - $index++; - if ($index < $count) { - $this->bar->advance(); - } else { - $this->bar->finish(); - } + $this->progressIndicator->advance(); } - // Tracks the current item for error messages - private $updating; - // An array of errors encountered while parsing - private $errors; - /** * Log a message to file, configurable by the --log-file parameter. * If a warning message is passed, we'll spit it to the console as well. diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php index 18e149b57d..6c43fec05a 100644 --- a/app/Helpers/Helper.php +++ b/app/Helpers/Helper.php @@ -1123,6 +1123,7 @@ class Helper 'png' => 'far fa-image', 'webp' => 'far fa-image', 'avif' => 'far fa-image', + 'svg' => 'fas fa-vector-square', // word 'doc' => 'far fa-file-word', 'docx' => 'far fa-file-word', @@ -1135,7 +1136,7 @@ class Helper //Text 'txt' => 'far fa-file-alt', 'rtf' => 'far fa-file-alt', - 'xml' => 'far fa-file-alt', + 'xml' => 'fas fa-code', // Misc 'pdf' => 'far fa-file-pdf', 'lic' => 'far fa-save', @@ -1148,41 +1149,7 @@ class Helper return 'far fa-file'; } - public static function show_file_inline($filename) - { - $extension = substr(strrchr($filename, '.'), 1); - if ($extension) { - switch ($extension) { - case 'jpg': - case 'jpeg': - case 'gif': - case 'png': - case 'webp': - case 'avif': - return true; - break; - default: - return false; - } - } - - return false; - } - - /** - * Generate a random encrypted password. - * - * @author Wes Hulette - * - * @since 5.0.0 - * - * @return string - */ - public static function generateEncyrptedPassword(): string - { - return bcrypt(self::generateUnencryptedPassword()); - } /** * Get a random unencrypted password. diff --git a/app/Helpers/StorageHelper.php b/app/Helpers/StorageHelper.php index 2cdab1d66c..47700f913a 100644 --- a/app/Helpers/StorageHelper.php +++ b/app/Helpers/StorageHelper.php @@ -7,6 +7,7 @@ use Illuminate\Http\Response; use Illuminate\Http\RedirectResponse; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\StreamedResponse; +use Illuminate\Contracts\Filesystem\FileNotFoundException; class StorageHelper { public static function downloader($filename, $disk = 'default') : BinaryFileResponse | RedirectResponse | StreamedResponse @@ -25,4 +26,64 @@ class StorageHelper return Storage::disk($disk)->download($filename); } } + + + /** + * This determines the file types that should be allowed inline and checks their fileinfo extension + * to determine that they are safe to display inline. + * + * @author [ + * @since v7.0.14 + * @param $file_with_path + * @return bool + */ + public static function allowSafeInline($file_with_path) { + + $allowed_inline = [ + 'pdf', + 'svg', + 'jpg', + 'gif', + 'svg', + 'avif', + 'webp', + 'png', + ]; + + + // The file exists and is allowed to be displayed inline + if (Storage::exists($file_with_path) && (in_array(pathinfo($file_with_path, PATHINFO_EXTENSION), $allowed_inline))) { + return true; + } + return false; + + } + + /** + * Decide whether to show the file inline or download it. + */ + public static function showOrDownloadFile($file, $filename) { + + $headers = []; + + if (request('inline') == 'true') { + + $headers = [ + 'Content-Disposition' => 'inline', + ]; + + // This is NOT allowed as inline - force it to be displayed as text in the browser + if (self::allowSafeInline($file) != true) { + $headers = array_merge($headers, ['Content-Type' => 'text/plain']); + } + } + + // Everything else seems okay, but the file doesn't exist on the server. + if (Storage::missing($file)) { + throw new FileNotFoundException(); + } + + return Storage::download($file, $filename, $headers); + + } } diff --git a/app/Http/Controllers/Accessories/AccessoriesFilesController.php b/app/Http/Controllers/Accessories/AccessoriesFilesController.php index b63c202d30..ebc1e4b8e0 100644 --- a/app/Http/Controllers/Accessories/AccessoriesFilesController.php +++ b/app/Http/Controllers/Accessories/AccessoriesFilesController.php @@ -106,50 +106,29 @@ class AccessoriesFilesController extends Controller * @param int $accessoryId * @param int $fileId */ - public function show($accessoryId = null, $fileId = null, $download = true) : View | RedirectResponse | Response | BinaryFileResponse | StreamedResponse + public function show($accessoryId = null, $fileId = null) : View | RedirectResponse | Response | BinaryFileResponse | StreamedResponse { - Log::debug('Private filesystem is: '.config('filesystems.default')); - $accessory = Accessory::find($accessoryId); - - // the accessory is valid - if (isset($accessory->id)) { + if ($accessory = Accessory::find($accessoryId)) { $this->authorize('view', $accessory); $this->authorize('accessories.files', $accessory); - if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) { - return redirect()->route('accessories.index')->with('error', trans('admin/users/message.log_record_not_found')); - } + if ($log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) { + $file = 'private_uploads/accessories/'.$log->filename; - $file = 'private_uploads/accessories/'.$log->filename; - - if (Storage::missing($file)) { - Log::debug('FILE DOES NOT EXISTS for '.$file); - Log::debug('URL should be '.Storage::url($file)); - - return response('File '.$file.' ('.Storage::url($file).') not found on server', 404) - ->header('Content-Type', 'text/plain'); - } else { - - // Display the file inline - if (request('inline') == 'true') { - $headers = [ - 'Content-Disposition' => 'inline', - ]; - return Storage::download($file, $log->filename, $headers); - } - - - // We have to override the URL stuff here, since local defaults in Laravel's Flysystem - // won't work, as they're not accessible via the web - if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer? - return StorageHelper::downloader($file); + try { + return StorageHelper::showOrDownloadFile($file, $log->filename); + } catch (\Exception $e) { + return redirect()->route('accessories.show', ['accessory' => $accessory])->with('error', trans('general.file_not_found')); } } + + return redirect()->route('accessories.show', ['accessory' => $accessory])->with('error', trans('general.log_record_not_found')); + } - return redirect()->route('accessories.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId])); + return redirect()->route('accessories.index')->with('error', trans('general.file_not_found')); } } diff --git a/app/Http/Controllers/Api/StatuslabelsController.php b/app/Http/Controllers/Api/StatuslabelsController.php index 754ebf7323..7e4851ff5a 100644 --- a/app/Http/Controllers/Api/StatuslabelsController.php +++ b/app/Http/Controllers/Api/StatuslabelsController.php @@ -95,7 +95,8 @@ class StatuslabelsController extends Controller $request->except('deployable', 'pending', 'archived'); if (! $request->filled('type')) { - return response()->json(Helper::formatStandardApiResponse('error', null, ['type' => ['Status label type is required.']]), 500); + + return response()->json(Helper::formatStandardApiResponse('error', null, ['type' => ['Status label type is required.']])); } $statuslabel = new Statuslabel; diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index 4714b29ea3..a9c8c26f14 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -14,6 +14,7 @@ use App\Http\Transformers\UsersTransformer; use App\Models\Actionlog; use App\Models\Asset; use App\Models\Accessory; +use App\Models\Company; use App\Models\Consumable; use App\Models\License; use App\Models\User; @@ -371,6 +372,7 @@ class UsersController extends Controller $user = new User; $user->fill($request->all()); + $user->company_id = Company::getIdForCurrentUser($request->input('company_id')); $user->created_by = auth()->id(); if ($request->has('permissions')) { @@ -452,6 +454,10 @@ class UsersController extends Controller $user->fill($request->all()); + if ($request->filled('company_id')) { + $user->company_id = Company::getIdForCurrentUser($request->input('company_id')); + } + if ($user->id == $request->input('manager_id')) { return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager')); } diff --git a/app/Http/Controllers/Assets/AssetFilesController.php b/app/Http/Controllers/Assets/AssetFilesController.php index b5a04759bb..d15055c4b2 100644 --- a/app/Http/Controllers/Assets/AssetFilesController.php +++ b/app/Http/Controllers/Assets/AssetFilesController.php @@ -61,43 +61,30 @@ class AssetFilesController extends Controller */ public function show($assetId = null, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse { - $asset = Asset::find($assetId); - // the asset is valid - if (isset($asset->id)) { + if ($asset = Asset::find($assetId)) { + $this->authorize('view', $asset); - if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) { - return response('No matching record for that asset/file', 500) - ->header('Content-Type', 'text/plain'); + if ($log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) { + $file = 'private_uploads/assets/'.$log->filename; + + if ($log->action_type == 'audit') { + $file = 'private_uploads/audits/'.$log->filename; + } + + try { + return StorageHelper::showOrDownloadFile($file, $log->filename); + } catch (\Exception $e) { + return redirect()->route('hardware.show', ['hardware' => $asset])->with('error', trans('general.file_not_found')); + } + } - $file = 'private_uploads/assets/'.$log->filename; - - if ($log->action_type == 'audit') { - $file = 'private_uploads/audits/'.$log->filename; - } - - if (! Storage::exists($file)) { - return response('File '.$file.' not found on server', 404) - ->header('Content-Type', 'text/plain'); - } - - if (request('inline') == 'true') { - - $headers = [ - 'Content-Disposition' => 'inline', - ]; - - return Storage::download($file, $log->filename, $headers); - } - - return StorageHelper::downloader($file); + return redirect()->route('hardware.show', ['hardware' => $asset])->with('error', trans('general.log_record_not_found')); } - // Prepare the error message - $error = trans('admin/hardware/message.does_not_exist', ['id' => $fileId]); - // Redirect to the hardware management page - return redirect()->route('hardware.index')->with('error', $error); + return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); + } /** diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index dda54f4c8d..50a758ac37 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -17,7 +17,6 @@ use App\Models\Location; use App\Models\Setting; use App\Models\Statuslabel; use App\Models\User; -use Illuminate\Support\Facades\Auth; use App\View\Label; use Carbon\Carbon; use Illuminate\Support\Facades\DB; diff --git a/app/Http/Controllers/Components/ComponentsFilesController.php b/app/Http/Controllers/Components/ComponentsFilesController.php index a7d42bb072..83468a0b10 100644 --- a/app/Http/Controllers/Components/ComponentsFilesController.php +++ b/app/Http/Controllers/Components/ComponentsFilesController.php @@ -112,40 +112,25 @@ class ComponentsFilesController extends Controller public function show($componentId = null, $fileId = null) { Log::debug('Private filesystem is: '.config('filesystems.default')); - $component = Component::find($componentId); + // the component is valid - if (isset($component->id)) { + if ($component = Component::find($componentId)) { $this->authorize('view', $component); $this->authorize('components.files', $component); - if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) { - return response('No matching record for that asset/file', 500) - ->header('Content-Type', 'text/plain'); - } + if ($log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) { - $file = 'private_uploads/components/'.$log->filename; + $file = 'private_uploads/components/'.$log->filename; - if (Storage::missing($file)) { - Log::debug('FILE DOES NOT EXISTS for '.$file); - Log::debug('URL should be '.Storage::url($file)); - - return response('File '.$file.' ('.Storage::url($file).') not found on server', 404) - ->header('Content-Type', 'text/plain'); - } else { - - // Display the file inline - if (request('inline') == 'true') { - $headers = [ - 'Content-Disposition' => 'inline', - ]; - return Storage::download($file, $log->filename, $headers); + try { + return StorageHelper::showOrDownloadFile($file, $log->filename); + } catch (\Exception $e) { + return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.file_not_found')); } - - if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer? - return StorageHelper::downloader($file); - } } + return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.log_record_not_found')); + } return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId])); diff --git a/app/Http/Controllers/Consumables/ConsumablesFilesController.php b/app/Http/Controllers/Consumables/ConsumablesFilesController.php index 35a4ae841e..054fdc0b81 100644 --- a/app/Http/Controllers/Consumables/ConsumablesFilesController.php +++ b/app/Http/Controllers/Consumables/ConsumablesFilesController.php @@ -104,7 +104,6 @@ class ConsumablesFilesController extends Controller * @since [v1.4] * @param int $consumableId * @param int $fileId - * @return \Symfony\Consumable\HttpFoundation\Response * @throws \Illuminate\Auth\Access\AuthorizationException */ public function show($consumableId = null, $fileId = null) @@ -116,36 +115,18 @@ class ConsumablesFilesController extends Controller $this->authorize('view', $consumable); $this->authorize('consumables.files', $consumable); - if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) { - return response('No matching record for that asset/file', 500) - ->header('Content-Type', 'text/plain'); - } + if ($log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) { + $file = 'private_uploads/consumables/'.$log->filename; - $file = 'private_uploads/consumables/'.$log->filename; - - if (Storage::missing($file)) { - Log::debug('FILE DOES NOT EXISTS for '.$file); - Log::debug('URL should be '.Storage::url($file)); - - return response('File '.$file.' ('.Storage::url($file).') not found on server', 404) - ->header('Content-Type', 'text/plain'); - } else { - - // Display the file inline - if (request('inline') == 'true') { - $headers = [ - 'Content-Disposition' => 'inline', - ]; - return Storage::download($file, $log->filename, $headers); - } - - - // We have to override the URL stuff here, since local defaults in Laravel's Flysystem - // won't work, as they're not accessible via the web - if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer? - return StorageHelper::downloader($file); + try { + return StorageHelper::showOrDownloadFile($file, $log->filename); + } catch (\Exception $e) { + return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.file_not_found')); } } + // The log record doesn't exist somehow + return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.log_record_not_found')); + } return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId])); diff --git a/app/Http/Controllers/Licenses/LicenseFilesController.php b/app/Http/Controllers/Licenses/LicenseFilesController.php index fa18e8cf48..6ab3cb7703 100644 --- a/app/Http/Controllers/Licenses/LicenseFilesController.php +++ b/app/Http/Controllers/Licenses/LicenseFilesController.php @@ -112,37 +112,19 @@ class LicenseFilesController extends Controller $this->authorize('view', $license); $this->authorize('licenses.files', $license); - if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) { - return response('No matching record for that asset/file', 500) - ->header('Content-Type', 'text/plain'); - } - - $file = 'private_uploads/licenses/'.$log->filename; - - if (Storage::missing($file)) { - Log::debug('NOT EXISTS for '.$file); - Log::debug('NOT EXISTS URL should be '.Storage::url($file)); - - return response('File '.$file.' ('.Storage::url($file).') not found on server', 404) - ->header('Content-Type', 'text/plain'); - } else { - - if (request('inline') == 'true') { - - $headers = [ - 'Content-Disposition' => 'inline', - ]; - - return Storage::download($file, $log->filename, $headers); - } - - // We have to override the URL stuff here, since local defaults in Laravel's Flysystem - // won't work, as they're not accessible via the web - if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer? - return StorageHelper::downloader($file); + if ($log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) { + $file = 'private_uploads/licenses/'.$log->filename; + try { + return StorageHelper::showOrDownloadFile($file, $log->filename); + } catch (\Exception $e) { + return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.file_not_found')); } } + + // The log record doesn't exist somehow + return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.log_record_not_found')); + } return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist', ['id' => $fileId])); diff --git a/app/Http/Controllers/Users/UserFilesController.php b/app/Http/Controllers/Users/UserFilesController.php index 9e5f322c03..e99bfe298f 100644 --- a/app/Http/Controllers/Users/UserFilesController.php +++ b/app/Http/Controllers/Users/UserFilesController.php @@ -7,9 +7,6 @@ use App\Http\Controllers\Controller; use App\Http\Requests\UploadFileRequest; use App\Models\Actionlog; use App\Models\User; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Input; -use Illuminate\Support\Facades\Response; use Symfony\Component\HttpFoundation\JsonResponse; use Illuminate\Support\Facades\Storage; @@ -116,31 +113,30 @@ class UserFilesController extends Controller public function show($userId = null, $fileId = null) { + if (empty($fileId)) { return redirect()->route('users.show')->with('error', 'Invalid file request'); } - $user = User::find($userId); - - // the license is valid - if (isset($user->id)) { + if ($user = User::find($userId)) { $this->authorize('view', $user); if ($log = Actionlog::whereNotNull('filename')->where('item_id', $user->id)->find($fileId)) { + $file = 'private_uploads/users/'.$log->filename; - // Display the file inline - if (request('inline') == 'true') { - $headers = [ - 'Content-Disposition' => 'inline', - ]; - return Storage::download('private_uploads/users/'.$log->filename, $log->filename, $headers); + try { + return StorageHelper::showOrDownloadFile($file, $log->filename); + } catch (\Exception $e) { + return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.file_not_found')); } - - return Storage::download('private_uploads/users/'.$log->filename); } - return redirect()->route('users.index')->with('error', trans('admin/users/message.log_record_not_found')); + // The log record doesn't exist somehow + return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.log_record_not_found')); + + + return redirect()->back()->with('error', trans('general.file_not_found')); } // Redirect to the user management page if the user doesn't exist diff --git a/app/Http/Requests/StoreAssetRequest.php b/app/Http/Requests/StoreAssetRequest.php index 26d01051b4..fb7469ac88 100644 --- a/app/Http/Requests/StoreAssetRequest.php +++ b/app/Http/Requests/StoreAssetRequest.php @@ -26,11 +26,19 @@ class StoreAssetRequest extends ImageUploadRequest public function prepareForValidation(): void { + // Guard against users passing in an array for company_id instead of an integer. + // If the company_id is not an integer then we simply use what was + // provided to be caught by model level validation later. + // The use of is_numeric accounts for 1 and '1'. + $idForCurrentUser = is_numeric($this->company_id) + ? Company::getIdForCurrentUser($this->company_id) + : $this->company_id; + $this->parseLastAuditDate(); $this->merge([ 'asset_tag' => $this->asset_tag ?? Asset::autoincrement_asset(), - 'company_id' => Company::getIdForCurrentUser($this->company_id), + 'company_id' => $idForCurrentUser, 'assigned_to' => $assigned_to ?? null, ]); } diff --git a/app/Http/Requests/Traits/MayContainCustomFields.php b/app/Http/Requests/Traits/MayContainCustomFields.php index 9a7f85e3a2..bbdf62893d 100644 --- a/app/Http/Requests/Traits/MayContainCustomFields.php +++ b/app/Http/Requests/Traits/MayContainCustomFields.php @@ -23,7 +23,7 @@ trait MayContainCustomFields return str_starts_with($attributes, '_snipeit_'); }); // if there are custom fields, find the one's that don't exist on the model's fieldset and add an error to the validator's error bag - if (count($request_fields) > 0) { + if (count($request_fields) > 0 && $validator->errors()->isEmpty()) { $request_fields->diff($asset_model?->fieldset?->fields?->pluck('db_column')) ->each(function ($request_field_name) use ($request_fields, $validator) { if (CustomField::where('db_column', $request_field_name)->exists()) { diff --git a/app/Http/Transformers/ActionlogsTransformer.php b/app/Http/Transformers/ActionlogsTransformer.php index d0605c747b..4e6341c8f3 100644 --- a/app/Http/Transformers/ActionlogsTransformer.php +++ b/app/Http/Transformers/ActionlogsTransformer.php @@ -141,6 +141,8 @@ class ActionlogsTransformer if ($actionlog->item) { if ($actionlog->itemType() == 'asset') { $file_url = route('show/assetfile', ['assetId' => $actionlog->item->id, 'fileId' => $actionlog->id]); + } elseif ($actionlog->itemType() == 'accessory') { + $file_url = route('show.accessoryfile', ['accessoryId' => $actionlog->item->id, 'fileId' => $actionlog->id]); } elseif ($actionlog->itemType() == 'license') { $file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]); } elseif ($actionlog->itemType() == 'user') { @@ -158,7 +160,6 @@ class ActionlogsTransformer [ 'url' => $file_url, 'filename' => $actionlog->filename, - 'inlineable' => (bool) Helper::show_file_inline($actionlog->filename), ] : null, 'item' => ($actionlog->item) ? [ @@ -346,4 +347,4 @@ class ActionlogsTransformer -} \ No newline at end of file +} diff --git a/app/Importer/Importer.php b/app/Importer/Importer.php index 47de5add4c..6f2816c7af 100644 --- a/app/Importer/Importer.php +++ b/app/Importer/Importer.php @@ -21,7 +21,6 @@ abstract class Importer * Id of User performing import * @var */ - protected $created_by; /** * Are we updating items in the import @@ -149,17 +148,23 @@ abstract class Importer { $headerRow = $this->csv->fetchOne(); $this->csv->setHeaderOffset(0); //explicitly sets the CSV document header record - $results = $this->normalizeInputArray($this->csv->getRecords($headerRow)); $this->populateCustomFields($headerRow); - DB::transaction(function () use (&$results) { + DB::transaction(function () use ($headerRow) { + $importedItemsCount = 0; Model::unguard(); - $resultsCount = count($results); - foreach ($results as $row) { + + foreach ($this->csv->getRecords($headerRow) as $row) { + //Lowercase header values to ensure we're comparing values properly. + $row = array_change_key_case($row, CASE_LOWER); + $this->handle($row); + + $importedItemsCount++; + if ($this->progressCallback) { - call_user_func($this->progressCallback, $resultsCount); + call_user_func($this->progressCallback, $importedItemsCount); } $this->log('------------- Action Summary ----------------'); @@ -237,22 +242,6 @@ abstract class Importer return $key; } - /** - * Used to lowercase header values to ensure we're comparing values properly. - * - * @param $results - * @return array - */ - public function normalizeInputArray($results) - { - $newArray = []; - foreach ($results as $index => $arrayToNormalize) { - $newArray[$index] = array_change_key_case($arrayToNormalize); - } - - return $newArray; - } - /** * Figure out the fieldname of the custom field * diff --git a/app/Livewire/CustomFieldSetDefaultValuesForModel.php b/app/Livewire/CustomFieldSetDefaultValuesForModel.php index 0ca733eb24..f45b62ce16 100644 --- a/app/Livewire/CustomFieldSetDefaultValuesForModel.php +++ b/app/Livewire/CustomFieldSetDefaultValuesForModel.php @@ -81,6 +81,12 @@ class CustomFieldSetDefaultValuesForModel extends Component { $this->fields->each(function ($field) { $this->selectedValues[$field->db_column] = $this->getSelectedValueForField($field); + + // if the element is a checkbox and the value was just sent to null, make it + // an array since Livewire can't bind to non-array values for checkboxes. + if ($field->element === 'checkbox' && is_null($this->selectedValues[$field->db_column])) { + $this->selectedValues[$field->db_column] = []; + } }); } diff --git a/app/Livewire/Importer.php b/app/Livewire/Importer.php index 3c6f7990ef..bffb3d763c 100644 --- a/app/Livewire/Importer.php +++ b/app/Livewire/Importer.php @@ -196,7 +196,6 @@ class Importer extends Component 'supplier' => trans('general.supplier'), 'purchase_cost' => trans('general.purchase_cost'), 'purchase_date' => trans('general.purchase_date'), - 'purchase_order' => trans('admin/licenses/form.purchase_order'), 'asset_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/general.asset')]), 'model_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]), 'manufacturer' => trans('general.manufacturer'), diff --git a/app/Models/Company.php b/app/Models/Company.php index 171d559542..8886da77f6 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -116,7 +116,7 @@ final class Company extends SnipeModel if ($current_user->company_id != null) { return $current_user->company_id; } else { - return static::getIdFromInput($unescaped_input); + return null; } } } diff --git a/composer.json b/composer.json index 6d89312578..d3637c3a4b 100644 --- a/composer.json +++ b/composer.json @@ -74,7 +74,6 @@ "ext-exif": "*" }, "require-dev": { - "brianium/paratest": "^7.0", "fakerphp/faker": "^1.16", "larastan/larastan": "^2.9", "mockery/mockery": "^1.4", diff --git a/composer.lock b/composer.lock index 3f79921b26..0631fc275e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3819ab4ef72eb77fabe494c0e746b83b", + "content-hash": "5341bc5be02b3c33e28e46e06dd99f29", "packages": [ { "name": "alek13/slack", @@ -11790,101 +11790,6 @@ ], "time": "2024-04-13T18:00:56+00:00" }, - { - "name": "brianium/paratest", - "version": "v7.3.1", - "source": { - "type": "git", - "url": "https://github.com/paratestphp/paratest.git", - "reference": "551f46f52a93177d873f3be08a1649ae886b4a30" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/551f46f52a93177d873f3be08a1649ae886b4a30", - "reference": "551f46f52a93177d873f3be08a1649ae886b4a30", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-simplexml": "*", - "fidry/cpu-core-counter": "^0.5.1 || ^1.0.0", - "jean85/pretty-package-versions": "^2.0.5", - "php": "~8.1.0 || ~8.2.0 || ~8.3.0", - "phpunit/php-code-coverage": "^10.1.7", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-timer": "^6.0", - "phpunit/phpunit": "^10.4.2", - "sebastian/environment": "^6.0.1", - "symfony/console": "^6.3.4 || ^7.0.0", - "symfony/process": "^6.3.4 || ^7.0.0" - }, - "require-dev": { - "doctrine/coding-standard": "^12.0.0", - "ext-pcov": "*", - "ext-posix": "*", - "infection/infection": "^0.27.6", - "phpstan/phpstan": "^1.10.40", - "phpstan/phpstan-deprecation-rules": "^1.1.4", - "phpstan/phpstan-phpunit": "^1.3.15", - "phpstan/phpstan-strict-rules": "^1.5.2", - "squizlabs/php_codesniffer": "^3.7.2", - "symfony/filesystem": "^6.3.1 || ^7.0.0" - }, - "bin": [ - "bin/paratest", - "bin/paratest.bat", - "bin/paratest_for_phpstorm" - ], - "type": "library", - "autoload": { - "psr-4": { - "ParaTest\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Brian Scaturro", - "email": "scaturrob@gmail.com", - "role": "Developer" - }, - { - "name": "Filippo Tessarotto", - "email": "zoeslam@gmail.com", - "role": "Developer" - } - ], - "description": "Parallel testing for PHP", - "homepage": "https://github.com/paratestphp/paratest", - "keywords": [ - "concurrent", - "parallel", - "phpunit", - "testing" - ], - "support": { - "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.3.1" - }, - "funding": [ - { - "url": "https://github.com/sponsors/Slamdunk", - "type": "github" - }, - { - "url": "https://paypal.me/filippotessarotto", - "type": "paypal" - } - ], - "time": "2023-10-31T09:24:17+00:00" - }, { "name": "clue/ndjson-react", "version": "v1.3.0", @@ -12781,65 +12686,6 @@ }, "time": "2020-07-09T08:09:16+00:00" }, - { - "name": "jean85/pretty-package-versions", - "version": "2.0.6", - "source": { - "type": "git", - "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4", - "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.0.0", - "php": "^7.1|^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.2", - "jean85/composer-provided-replaced-stub-package": "^1.0", - "phpstan/phpstan": "^1.4", - "phpunit/phpunit": "^7.5|^8.5|^9.4", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Jean85\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Alessandro Lai", - "email": "alessandro.lai85@gmail.com" - } - ], - "description": "A library to get pretty versions strings of installed dependencies", - "keywords": [ - "composer", - "package", - "release", - "versions" - ], - "support": { - "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6" - }, - "time": "2024-03-08T09:58:59+00:00" - }, { "name": "justinrainbow/json-schema", "version": "5.3.0", diff --git a/config/app.php b/config/app.php index bc74b4dd05..39898ff437 100755 --- a/config/app.php +++ b/config/app.php @@ -280,7 +280,6 @@ return [ Illuminate\Redis\RedisServiceProvider::class, Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, Illuminate\Session\SessionServiceProvider::class, -// Illuminate\Translation\TranslationServiceProvider::class, //replaced on next line App\Providers\SnipeTranslationServiceProvider::class, //we REPLACE the default Laravel translator with our own Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, @@ -373,7 +372,7 @@ return [ 'Image' => Intervention\Image\ImageServiceProvider::class, 'Carbon' => Carbon\Carbon::class, 'Helper' => App\Helpers\Helper::class, - // makes it much easier to use 'Helper::blah' in blades (which is where we usually use this) + 'StorageHelper' => App\Helpers\StorageHelper::class, 'Icon' => App\Helpers\IconHelper::class, 'Socialite' => Laravel\Socialite\Facades\Socialite::class, diff --git a/config/version.php b/config/version.php index e7eea2adfb..2249e8dbd3 100644 --- a/config/version.php +++ b/config/version.php @@ -1,10 +1,10 @@ 'v7.0.13', - 'full_app_version' => 'v7.0.13 - build 15514-gdc0949da7', - 'build_version' => '15514', + 'full_app_version' => 'v7.0.13 - build 15666-g03b01689b', + 'build_version' => '15666', 'prerelease_version' => '', - 'hash_version' => 'gdc0949da7', - 'full_hash' => 'v7.0.13-265-gdc0949da7', + 'hash_version' => 'g03b01689b', + 'full_hash' => 'v7.0.13-144-g03b01689b', 'branch' => 'develop', ); \ No newline at end of file diff --git a/resources/lang/en-US/general.php b/resources/lang/en-US/general.php index 34315c99a8..c2ca8bf4c7 100644 --- a/resources/lang/en-US/general.php +++ b/resources/lang/en-US/general.php @@ -434,6 +434,7 @@ return [ 'alt_uploaded_image_thumbnail' => 'Uploaded thumbnail', 'placeholder_kit' => 'Select a kit', 'file_not_found' => 'File not found', + 'log_record_not_found' => 'No record for that log entry was found.', 'preview_not_available' => '(no preview)', 'setup' => 'Setup', 'pre_flight' => 'Pre-Flight', diff --git a/resources/views/accessories/view.blade.php b/resources/views/accessories/view.blade.php index 1f7e5f0b9a..1e3568f0ef 100644 --- a/resources/views/accessories/view.blade.php +++ b/resources/views/accessories/view.blade.php @@ -155,102 +155,16 @@ @can('accessories.files', $accessory)
-
-
-
- - - - - - - - - - - - - - - @if ($accessory->uploads->count() > 0) - @foreach ($accessory->uploads as $file) - - - - - - - - - - - - @endforeach - @else - - - - @endif - -
{{trans('general.file_type')}}{{ trans('general.image') }}{{ trans('general.file_name') }}{{ trans('general.filesize') }}{{ trans('general.notes') }}{{ trans('general.download') }}{{ trans('general.created_at') }}{{ trans('table.actions') }}
- - - {{ Helper::filetype_icon($file->filename) }} - - - @if ($file->filename) - @if ( Helper::checkUploadIsImage($file->get_src('accessories'))) - - @endif - @endif - - {{ $file->filename }} - - {{ @Helper::formatFilesizeUnits(Storage::exists('private_uploads/accessories/'.$file->filename) ? Storage::size('private_uploads/accessories/'.$file->filename) : '') }} - - @if ($file->note) - {{ $file->note }} - @endif - - @if ($file->filename) - - - {{ trans('general.download') }} - - - - - - - @endif - {{ $file->created_at }} - - - {{ trans('general.delete') }} - -
{{ trans('general.no_results') }}
+
+
+ +
-
-
-
+
@endcan @@ -265,7 +179,8 @@ @if ($accessory->image!='')
- {{ $accessory->name }} + + {{ $accessory->name }}
@endif diff --git a/resources/views/blade/filestable.blade.php b/resources/views/blade/filestable.blade.php new file mode 100644 index 0000000000..50c24dd6c9 --- /dev/null +++ b/resources/views/blade/filestable.blade.php @@ -0,0 +1,142 @@ + +@props([ + 'filepath', + 'object', + 'showfile_routename', + 'deletefile_routename', +]) + + +
+ + + + + + + + + + + + + + + + + + @foreach ($object->uploads as $file) + + + + + + + + + + + + + + + + @endforeach + + +
+ {{trans('general.id')}} + + {{trans('general.file_type')}} + + {{ trans('general.image') }} + + {{ trans('general.file_name') }} + + {{ trans('general.filesize') }} + + {{ trans('general.notes') }} + + {{ trans('general.download') }} + + {{ trans('general.created_at') }} + + {{ trans('general.created_by') }} + + {{ trans('table.actions') }} +
+ {{ $file->id }} + + @if (Storage::exists($filepath.$file->filename)) + {{ pathinfo($filepath.$file->filename, PATHINFO_EXTENSION) }} + + @endif + + + @if (($file->filename) && (Storage::exists($filepath.$file->filename))) + @if (Helper::checkUploadIsImage($file->get_src(str_plural(strtolower(class_basename(get_class($object))))))) + + + + @else + {{ trans('general.preview_not_available') }} + @endif + @else + + {{ trans('general.file_not_found') }} + @endif + + + {{ $file->filename }} + + {{ (Storage::exists($filepath.$file->filename)) ? Helper::formatFilesizeUnits(Storage::size($filepath.$file->filename)) : '' }} + + @if ($file->note) + {{ $file->note }} + @endif + + @if ($file->filename) + @if (Storage::exists($filepath.$file->filename)) + + + + {{ trans('general.download') }} + + + + + + + @endif + @endif + + {{ $file->created_at }} + + {{ $file->created_by }} + + + + {{ trans('general.delete') }} + +
+
+ \ No newline at end of file diff --git a/resources/views/components/view.blade.php b/resources/views/components/view.blade.php index 287b0b23a8..ba3a6d6e24 100644 --- a/resources/views/components/view.blade.php +++ b/resources/views/components/view.blade.php @@ -140,96 +140,14 @@ @can('components.files', $component)
- -
- - - - - - - - - - - - - - - @if ($component->uploads->count() > 0) - @foreach ($component->uploads as $file) - - - - - - - - - - - - @endforeach - @else - - - - @endif - -
{{trans('general.file_type')}}{{ trans('general.image') }}{{ trans('general.file_name') }}{{ trans('general.filesize') }}{{ trans('general.notes') }}{{ trans('general.download') }}{{ trans('general.created_at') }}{{ trans('table.actions') }}
- - {{ Helper::filetype_icon($file->filename) }} - - - @if ($file->filename) - @if ( Helper::checkUploadIsImage($file->get_src('components'))) - - @endif - @endif - - {{ $file->filename }} - - {{ @Helper::formatFilesizeUnits(Storage::exists('private_uploads/components/'.$file->filename) ? Storage::size('private_uploads/components/'.$file->filename) : '') }} - - @if ($file->note) - {{ $file->note }} - @endif - - @if ($file->filename) - - - {{ trans('general.download') }} - - - - - - - @endif - {{ $file->created_at }} - - - {{ trans('general.delete') }} - -
{{ trans('general.no_results') }}
+
+
+ +
@endcan @@ -243,7 +161,7 @@
@if ($component->image!='') diff --git a/resources/views/consumables/view.blade.php b/resources/views/consumables/view.blade.php index 88ea19195f..ac95e6391b 100644 --- a/resources/views/consumables/view.blade.php +++ b/resources/views/consumables/view.blade.php @@ -89,7 +89,7 @@ @if ($consumable->image!='') @endif @@ -428,102 +428,18 @@
+
+
+ -
-
- - - - - - - - - - - - - - - - @if ($consumable->uploads->count() > 0) - @foreach ($consumable->uploads as $file) - - - - - - - - - - - - @endforeach - @else - - - - @endif - -
{{trans('general.file_type')}}{{ trans('general.image') }}{{ trans('general.file_name') }}{{ trans('general.filesize') }}{{ trans('general.notes') }}{{ trans('general.download') }}{{ trans('general.created_at') }}{{ trans('table.actions') }}
- - {{ Helper::filetype_icon($file->filename) }} - - - @if ($file->filename) - @if ( Helper::checkUploadIsImage($file->get_src('consumables'))) - - @endif - @endif - - {{ $file->filename }} - - {{ @Helper::formatFilesizeUnits(Storage::exists('private_uploads/consumables/'.$file->filename) ? Storage::size('private_uploads/consumables/'.$file->filename) : '') }} - - @if ($file->note) - {!! nl2br(Helper::parseEscapedMarkedownInline($file->note)) !!} - @endif - - @if ($file->filename) - - - {{ trans('general.download') }} - - - - - - @endif - {{ $file->created_at }} - - - {{ trans('general.delete') }} - - -
{{ trans('general.no_results') }}
-
-
+
+
diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index 39a5d8b751..9eb2eb5386 100755 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -34,7 +34,7 @@

{{ number_format(\App\Models\Asset::AssetsForShow()->count()) }}

-

{{ strtolower(trans('general.assets')) }}

+

{{ trans('general.assets') }}