Merge branch 'develop' into fixes/get-id-for-current-user

This commit is contained in:
Marcus Moore 2024-10-22 09:38:31 -07:00
commit d10fe77ee7
No known key found for this signature in database
39 changed files with 443 additions and 1198 deletions

View file

@ -76,4 +76,4 @@ jobs:
DB_DATABASE: snipeit DB_DATABASE: snipeit
DB_PORT: ${{ job.services.mysql.ports[3306] }} DB_PORT: ${{ job.services.mysql.ports[3306] }}
DB_USERNAME: root DB_USERNAME: root
run: php artisan test --parallel run: php artisan test

View file

@ -74,4 +74,4 @@ jobs:
DB_PORT: ${{ job.services.postgresql.ports[5432] }} DB_PORT: ${{ job.services.postgresql.ports[5432] }}
DB_USERNAME: snipeit DB_USERNAME: snipeit
DB_PASSWORD: password DB_PASSWORD: password
run: php artisan test --parallel run: php artisan test

View file

@ -58,4 +58,4 @@ jobs:
- name: Execute tests (Unit and Feature tests) via PHPUnit - name: Execute tests (Unit and Feature tests) via PHPUnit
env: env:
DB_CONNECTION: sqlite_testing DB_CONNECTION: sqlite_testing
run: php artisan test --parallel run: php artisan test

View file

@ -6,6 +6,7 @@ use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Illuminate\Support\Facades\Log; 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('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); //600 seconds = 10 minutes
ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M')); ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M'));
@ -29,6 +30,11 @@ class ObjectImportCommand extends Command
*/ */
protected $description = 'Import Items from CSV'; protected $description = 'Import Items from CSV';
/**
* The progress indicator instance.
*/
protected ProgressIndicator $progressIndicator;
/** /**
* Create a new command instance. * Create a new command instance.
* *
@ -39,8 +45,6 @@ class ObjectImportCommand extends Command
parent::__construct(); parent::__construct();
} }
private $bar;
/** /**
* Execute the console command. * Execute the console command.
* *
@ -48,6 +52,8 @@ class ObjectImportCommand extends Command
*/ */
public function handle() public function handle()
{ {
$this->progressIndicator = new ProgressIndicator($this->output);
$filename = $this->argument('filename'); $filename = $this->argument('filename');
$class = title_case($this->option('item-type')); $class = title_case($this->option('item-type'));
$classString = "App\\Importer\\{$class}Importer"; $classString = "App\\Importer\\{$class}Importer";
@ -61,45 +67,24 @@ class ObjectImportCommand extends Command
// This $logFile/useFiles() bit is currently broken, so commenting it out for now // This $logFile/useFiles() bit is currently broken, so commenting it out for now
// $logFile = $this->option('logfile'); // $logFile = $this->option('logfile');
// Log::useFiles($logFile); // Log::useFiles($logFile);
$this->comment('======= Importing Items from '.$filename.' ========='); $this->progressIndicator->start('======= Importing Items from '.$filename.' =========');
$importer->import(); $importer->import();
$this->bar = null; $this->progressIndicator->finish('Import finished.');
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('');
} }
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->progressIndicator->advance();
$this->bar = $this->output->createProgressBar($count);
} }
static $index = 0;
$index++;
if ($index < $count) {
$this->bar->advance();
} else {
$this->bar->finish();
}
}
// 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. * Log a message to file, configurable by the --log-file parameter.

View file

@ -1123,6 +1123,7 @@ class Helper
'png' => 'far fa-image', 'png' => 'far fa-image',
'webp' => 'far fa-image', 'webp' => 'far fa-image',
'avif' => 'far fa-image', 'avif' => 'far fa-image',
'svg' => 'fas fa-vector-square',
// word // word
'doc' => 'far fa-file-word', 'doc' => 'far fa-file-word',
'docx' => 'far fa-file-word', 'docx' => 'far fa-file-word',
@ -1135,7 +1136,7 @@ class Helper
//Text //Text
'txt' => 'far fa-file-alt', 'txt' => 'far fa-file-alt',
'rtf' => 'far fa-file-alt', 'rtf' => 'far fa-file-alt',
'xml' => 'far fa-file-alt', 'xml' => 'fas fa-code',
// Misc // Misc
'pdf' => 'far fa-file-pdf', 'pdf' => 'far fa-file-pdf',
'lic' => 'far fa-save', 'lic' => 'far fa-save',
@ -1148,41 +1149,7 @@ class Helper
return 'far fa-file'; 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 <jwhulette@gmail.com>
*
* @since 5.0.0
*
* @return string
*/
public static function generateEncyrptedPassword(): string
{
return bcrypt(self::generateUnencryptedPassword());
}
/** /**
* Get a random unencrypted password. * Get a random unencrypted password.

View file

@ -7,6 +7,7 @@ use Illuminate\Http\Response;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpFoundation\StreamedResponse;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
class StorageHelper class StorageHelper
{ {
public static function downloader($filename, $disk = 'default') : BinaryFileResponse | RedirectResponse | StreamedResponse public static function downloader($filename, $disk = 'default') : BinaryFileResponse | RedirectResponse | StreamedResponse
@ -25,4 +26,64 @@ class StorageHelper
return Storage::disk($disk)->download($filename); 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 <A. Gianotto> [<snipe@snipe.net]>
* @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);
}
} }

View file

@ -106,50 +106,29 @@ class AccessoriesFilesController extends Controller
* @param int $accessoryId * @param int $accessoryId
* @param int $fileId * @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 // the accessory is valid
if (isset($accessory->id)) { if ($accessory = Accessory::find($accessoryId)) {
$this->authorize('view', $accessory); $this->authorize('view', $accessory);
$this->authorize('accessories.files', $accessory); $this->authorize('accessories.files', $accessory);
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) { 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'));
}
$file = 'private_uploads/accessories/'.$log->filename; $file = 'private_uploads/accessories/'.$log->filename;
if (Storage::missing($file)) { try {
Log::debug('FILE DOES NOT EXISTS for '.$file); return StorageHelper::showOrDownloadFile($file, $log->filename);
Log::debug('URL should be '.Storage::url($file)); } catch (\Exception $e) {
return redirect()->route('accessories.show', ['accessory' => $accessory])->with('error', trans('general.file_not_found'));
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);
} }
} }
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'));
} }
} }

View file

@ -95,7 +95,8 @@ class StatuslabelsController extends Controller
$request->except('deployable', 'pending', 'archived'); $request->except('deployable', 'pending', 'archived');
if (! $request->filled('type')) { 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; $statuslabel = new Statuslabel;

View file

@ -14,6 +14,7 @@ use App\Http\Transformers\UsersTransformer;
use App\Models\Actionlog; use App\Models\Actionlog;
use App\Models\Asset; use App\Models\Asset;
use App\Models\Accessory; use App\Models\Accessory;
use App\Models\Company;
use App\Models\Consumable; use App\Models\Consumable;
use App\Models\License; use App\Models\License;
use App\Models\User; use App\Models\User;
@ -371,6 +372,7 @@ class UsersController extends Controller
$user = new User; $user = new User;
$user->fill($request->all()); $user->fill($request->all());
$user->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$user->created_by = auth()->id(); $user->created_by = auth()->id();
if ($request->has('permissions')) { if ($request->has('permissions')) {
@ -452,6 +454,10 @@ class UsersController extends Controller
$user->fill($request->all()); $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')) { if ($user->id == $request->input('manager_id')) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager')); return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager'));
} }

View file

@ -61,43 +61,30 @@ class AssetFilesController extends Controller
*/ */
public function show($assetId = null, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse public function show($assetId = null, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse
{ {
$asset = Asset::find($assetId); if ($asset = Asset::find($assetId)) {
// the asset is valid
if (isset($asset->id)) {
$this->authorize('view', $asset); $this->authorize('view', $asset);
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) { 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');
}
$file = 'private_uploads/assets/'.$log->filename; $file = 'private_uploads/assets/'.$log->filename;
if ($log->action_type == 'audit') { if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename; $file = 'private_uploads/audits/'.$log->filename;
} }
if (! Storage::exists($file)) { try {
return response('File '.$file.' not found on server', 404) return StorageHelper::showOrDownloadFile($file, $log->filename);
->header('Content-Type', 'text/plain'); } catch (\Exception $e) {
return redirect()->route('hardware.show', ['hardware' => $asset])->with('error', trans('general.file_not_found'));
} }
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', trans('admin/hardware/message.does_not_exist'));
return redirect()->route('hardware.index')->with('error', $error);
} }
/** /**

View file

@ -17,7 +17,6 @@ use App\Models\Location;
use App\Models\Setting; use App\Models\Setting;
use App\Models\Statuslabel; use App\Models\Statuslabel;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\Auth;
use App\View\Label; use App\View\Label;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;

View file

@ -52,6 +52,10 @@ class BulkAssetsController extends Controller
} }
$asset_ids = $request->input('ids'); $asset_ids = $request->input('ids');
if ($request->input('bulk_actions') === 'checkout') {
$request->session()->flashInput(['selected_assets' => $asset_ids]);
return redirect()->route('hardware.bulkcheckout.show');
}
// Figure out where we need to send the user after the update is complete, and store that in the session // Figure out where we need to send the user after the update is complete, and store that in the session
$bulk_back_url = request()->headers->get('referer'); $bulk_back_url = request()->headers->get('referer');
@ -571,31 +575,34 @@ class BulkAssetsController extends Controller
} }
$errors = []; $errors = [];
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, $errors, $asset_ids, $request) { DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, &$errors, $asset_ids, $request) { //NOTE: $errors is passsed by reference!
foreach ($asset_ids as $asset_id) { foreach ($asset_ids as $asset_id) {
$asset = Asset::findOrFail($asset_id); $asset = Asset::findOrFail($asset_id);
$this->authorize('checkout', $asset); $this->authorize('checkout', $asset);
$error = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null); $checkout_success = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null);
//TODO - I think this logic is duplicated in the checkOut method?
if ($target->location_id != '') { if ($target->location_id != '') {
$asset->location_id = $target->location_id; $asset->location_id = $target->location_id;
$asset->unsetEventDispatcher(); // TODO - I don't know why this is being saved without events
$asset::withoutEvents(function () use ($asset) {
$asset->save(); $asset->save();
});
} }
if ($error) { if (!$checkout_success) {
array_merge_recursive($errors, $asset->getErrors()->toArray()); $errors = array_merge_recursive($errors, $asset->getErrors()->toArray());
} }
} }
}); });
if (! $errors) { if (! $errors) {
// Redirect to the new asset page // Redirect to the new asset page
return redirect()->to('hardware')->with('success', trans('admin/hardware/message.checkout.success')); return redirect()->to('hardware')->with('success', trans_choice('admin/hardware/message.multi-checkout.success', $asset_ids));
} }
// Redirect to the asset management page with error // Redirect to the asset management page with error
return redirect()->route('hardware.bulkcheckout.show')->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($errors); return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans_choice('admin/hardware/message.multi-checkout.error', $asset_ids))->withErrors($errors);
} catch (ModelNotFoundException $e) { } catch (ModelNotFoundException $e) {
return redirect()->route('hardware.bulkcheckout.show')->with('error', $e->getErrors()); return redirect()->route('hardware.bulkcheckout.show')->with('error', $e->getErrors());
} }

View file

@ -112,40 +112,25 @@ class ComponentsFilesController extends Controller
public function show($componentId = null, $fileId = null) public function show($componentId = null, $fileId = null)
{ {
Log::debug('Private filesystem is: '.config('filesystems.default')); Log::debug('Private filesystem is: '.config('filesystems.default'));
$component = Component::find($componentId);
// the component is valid // the component is valid
if (isset($component->id)) { if ($component = Component::find($componentId)) {
$this->authorize('view', $component); $this->authorize('view', $component);
$this->authorize('components.files', $component); $this->authorize('components.files', $component);
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) { 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');
}
$file = 'private_uploads/components/'.$log->filename; $file = 'private_uploads/components/'.$log->filename;
if (Storage::missing($file)) { try {
Log::debug('FILE DOES NOT EXISTS for '.$file); return StorageHelper::showOrDownloadFile($file, $log->filename);
Log::debug('URL should be '.Storage::url($file)); } catch (\Exception $e) {
return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.file_not_found'));
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);
}
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])); return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));

View file

@ -104,7 +104,6 @@ class ConsumablesFilesController extends Controller
* @since [v1.4] * @since [v1.4]
* @param int $consumableId * @param int $consumableId
* @param int $fileId * @param int $fileId
* @return \Symfony\Consumable\HttpFoundation\Response
* @throws \Illuminate\Auth\Access\AuthorizationException * @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function show($consumableId = null, $fileId = null) public function show($consumableId = null, $fileId = null)
@ -116,36 +115,18 @@ class ConsumablesFilesController extends Controller
$this->authorize('view', $consumable); $this->authorize('view', $consumable);
$this->authorize('consumables.files', $consumable); $this->authorize('consumables.files', $consumable);
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) { 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');
}
$file = 'private_uploads/consumables/'.$log->filename; $file = 'private_uploads/consumables/'.$log->filename;
if (Storage::missing($file)) { try {
Log::debug('FILE DOES NOT EXISTS for '.$file); return StorageHelper::showOrDownloadFile($file, $log->filename);
Log::debug('URL should be '.Storage::url($file)); } catch (\Exception $e) {
return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.file_not_found'));
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);
} }
} }
// 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])); return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));

View file

@ -112,37 +112,19 @@ class LicenseFilesController extends Controller
$this->authorize('view', $license); $this->authorize('view', $license);
$this->authorize('licenses.files', $license); $this->authorize('licenses.files', $license);
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) { 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; $file = 'private_uploads/licenses/'.$log->filename;
if (Storage::missing($file)) { try {
Log::debug('NOT EXISTS for '.$file); return StorageHelper::showOrDownloadFile($file, $log->filename);
Log::debug('NOT EXISTS URL should be '.Storage::url($file)); } catch (\Exception $e) {
return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.file_not_found'));
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);
} }
} }
// 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])); return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist', ['id' => $fileId]));

View file

@ -7,9 +7,6 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest; use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog; use App\Models\Actionlog;
use App\Models\User; 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 Symfony\Component\HttpFoundation\JsonResponse;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -116,31 +113,30 @@ class UserFilesController extends Controller
public function show($userId = null, $fileId = null) public function show($userId = null, $fileId = null)
{ {
if (empty($fileId)) { if (empty($fileId)) {
return redirect()->route('users.show')->with('error', 'Invalid file request'); return redirect()->route('users.show')->with('error', 'Invalid file request');
} }
$user = User::find($userId); if ($user = User::find($userId)) {
// the license is valid
if (isset($user->id)) {
$this->authorize('view', $user); $this->authorize('view', $user);
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $user->id)->find($fileId)) { if ($log = Actionlog::whereNotNull('filename')->where('item_id', $user->id)->find($fileId)) {
$file = 'private_uploads/users/'.$log->filename;
// Display the file inline try {
if (request('inline') == 'true') { return StorageHelper::showOrDownloadFile($file, $log->filename);
$headers = [ } catch (\Exception $e) {
'Content-Disposition' => 'inline', return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.file_not_found'));
]; }
return Storage::download('private_uploads/users/'.$log->filename, $log->filename, $headers);
} }
return Storage::download('private_uploads/users/'.$log->filename); // The log record doesn't exist somehow
} return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.log_record_not_found'));
return redirect()->route('users.index')->with('error', trans('admin/users/message.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 // Redirect to the user management page if the user doesn't exist

View file

@ -23,7 +23,7 @@ trait MayContainCustomFields
return str_starts_with($attributes, '_snipeit_'); 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 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')) $request_fields->diff($asset_model?->fieldset?->fields?->pluck('db_column'))
->each(function ($request_field_name) use ($request_fields, $validator) { ->each(function ($request_field_name) use ($request_fields, $validator) {
if (CustomField::where('db_column', $request_field_name)->exists()) { if (CustomField::where('db_column', $request_field_name)->exists()) {

View file

@ -141,6 +141,8 @@ class ActionlogsTransformer
if ($actionlog->item) { if ($actionlog->item) {
if ($actionlog->itemType() == 'asset') { if ($actionlog->itemType() == 'asset') {
$file_url = route('show/assetfile', ['assetId' => $actionlog->item->id, 'fileId' => $actionlog->id]); $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') { } elseif ($actionlog->itemType() == 'license') {
$file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]); $file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
} elseif ($actionlog->itemType() == 'user') { } elseif ($actionlog->itemType() == 'user') {
@ -158,7 +160,6 @@ class ActionlogsTransformer
[ [
'url' => $file_url, 'url' => $file_url,
'filename' => $actionlog->filename, 'filename' => $actionlog->filename,
'inlineable' => (bool) Helper::show_file_inline($actionlog->filename),
] : null, ] : null,
'item' => ($actionlog->item) ? [ 'item' => ($actionlog->item) ? [

View file

@ -21,7 +21,6 @@ abstract class Importer
* Id of User performing import * Id of User performing import
* @var * @var
*/ */
protected $created_by; protected $created_by;
/** /**
* Are we updating items in the import * Are we updating items in the import
@ -149,17 +148,23 @@ abstract class Importer
{ {
$headerRow = $this->csv->fetchOne(); $headerRow = $this->csv->fetchOne();
$this->csv->setHeaderOffset(0); //explicitly sets the CSV document header record $this->csv->setHeaderOffset(0); //explicitly sets the CSV document header record
$results = $this->normalizeInputArray($this->csv->getRecords($headerRow));
$this->populateCustomFields($headerRow); $this->populateCustomFields($headerRow);
DB::transaction(function () use (&$results) { DB::transaction(function () use ($headerRow) {
$importedItemsCount = 0;
Model::unguard(); 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); $this->handle($row);
$importedItemsCount++;
if ($this->progressCallback) { if ($this->progressCallback) {
call_user_func($this->progressCallback, $resultsCount); call_user_func($this->progressCallback, $importedItemsCount);
} }
$this->log('------------- Action Summary ----------------'); $this->log('------------- Action Summary ----------------');
@ -237,22 +242,6 @@ abstract class Importer
return $key; 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 * Figure out the fieldname of the custom field
* *

View file

@ -196,7 +196,6 @@ class Importer extends Component
'supplier' => trans('general.supplier'), 'supplier' => trans('general.supplier'),
'purchase_cost' => trans('general.purchase_cost'), 'purchase_cost' => trans('general.purchase_cost'),
'purchase_date' => trans('general.purchase_date'), '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')]), 'asset_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/general.asset')]),
'model_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]), 'model_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]),
'manufacturer' => trans('general.manufacturer'), 'manufacturer' => trans('general.manufacturer'),

View file

@ -8,9 +8,6 @@ trait CompanyableTrait
* This trait is used to scope models to the current company. To use this scope on companyable models, * This trait is used to scope models to the current company. To use this scope on companyable models,
* we use the "use Companyable;" statement at the top of the mode. * we use the "use Companyable;" statement at the top of the mode.
* *
* We CANNOT USE THIS ON USERS, as it causes an infinite loop and prevents users from logging in, since this scope will be
* applied to the currently logged in (or logging in) user in addition to the user model for viewing lists of users.
*
* @see \App\Models\Company\Company::scopeCompanyables() * @see \App\Models\Company\Company::scopeCompanyables()
* @return void * @return void
*/ */

View file

@ -74,7 +74,6 @@
"ext-exif": "*" "ext-exif": "*"
}, },
"require-dev": { "require-dev": {
"brianium/paratest": "^7.0",
"fakerphp/faker": "^1.16", "fakerphp/faker": "^1.16",
"larastan/larastan": "^2.9", "larastan/larastan": "^2.9",
"mockery/mockery": "^1.4", "mockery/mockery": "^1.4",

156
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "3819ab4ef72eb77fabe494c0e746b83b", "content-hash": "5341bc5be02b3c33e28e46e06dd99f29",
"packages": [ "packages": [
{ {
"name": "alek13/slack", "name": "alek13/slack",
@ -11790,101 +11790,6 @@
], ],
"time": "2024-04-13T18:00:56+00:00" "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", "name": "clue/ndjson-react",
"version": "v1.3.0", "version": "v1.3.0",
@ -12781,65 +12686,6 @@
}, },
"time": "2020-07-09T08:09:16+00:00" "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", "name": "justinrainbow/json-schema",
"version": "5.3.0", "version": "5.3.0",

View file

@ -280,7 +280,6 @@ return [
Illuminate\Redis\RedisServiceProvider::class, Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::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 App\Providers\SnipeTranslationServiceProvider::class, //we REPLACE the default Laravel translator with our own
Illuminate\Validation\ValidationServiceProvider::class, Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class, Illuminate\View\ViewServiceProvider::class,
@ -373,7 +372,7 @@ return [
'Image' => Intervention\Image\ImageServiceProvider::class, 'Image' => Intervention\Image\ImageServiceProvider::class,
'Carbon' => Carbon\Carbon::class, 'Carbon' => Carbon\Carbon::class,
'Helper' => App\Helpers\Helper::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, 'Icon' => App\Helpers\IconHelper::class,
'Socialite' => Laravel\Socialite\Facades\Socialite::class, 'Socialite' => Laravel\Socialite\Facades\Socialite::class,

View file

@ -1,10 +1,10 @@
<?php <?php
return array ( return array (
'app_version' => 'v7.0.13', 'app_version' => 'v7.0.13',
'full_app_version' => 'v7.0.13 - build 15514-gdc0949da7', 'full_app_version' => 'v7.0.13 - build 15666-g03b01689b',
'build_version' => '15514', 'build_version' => '15666',
'prerelease_version' => '', 'prerelease_version' => '',
'hash_version' => 'gdc0949da7', 'hash_version' => 'g03b01689b',
'full_hash' => 'v7.0.13-265-gdc0949da7', 'full_hash' => 'v7.0.13-144-g03b01689b',
'branch' => 'develop', 'branch' => 'develop',
); );

View file

@ -79,6 +79,11 @@ return [
'no_assets_selected' => 'You must select at least one asset from the list', 'no_assets_selected' => 'You must select at least one asset from the list',
], ],
'multi-checkout' => [
'error' => 'Asset was not checked out, please try again|Assets were not checked out, please try again',
'success' => 'Asset checked out successfully.|Assets checked out successfully.',
],
'checkin' => [ 'checkin' => [
'error' => 'Asset was not checked in, please try again', 'error' => 'Asset was not checked in, please try again',
'success' => 'Asset checked in successfully.', 'success' => 'Asset checked in successfully.',

View file

@ -434,6 +434,7 @@ return [
'alt_uploaded_image_thumbnail' => 'Uploaded thumbnail', 'alt_uploaded_image_thumbnail' => 'Uploaded thumbnail',
'placeholder_kit' => 'Select a kit', 'placeholder_kit' => 'Select a kit',
'file_not_found' => 'File not found', 'file_not_found' => 'File not found',
'log_record_not_found' => 'No record for that log entry was found.',
'preview_not_available' => '(no preview)', 'preview_not_available' => '(no preview)',
'setup' => 'Setup', 'setup' => 'Setup',
'pre_flight' => 'Pre-Flight', 'pre_flight' => 'Pre-Flight',

View file

@ -155,99 +155,13 @@
@can('accessories.files', $accessory) @can('accessories.files', $accessory)
<div class="tab-pane" id="files"> <div class="tab-pane" id="files">
<div class="table table-responsive">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<table <x-filestable
data-cookie-id-table="accessoryUploadsTable" filepath="private_uploads/accessories/"
data-id-table="accessoryUploadsTable" showfile_routename="show.accessoryfile"
id="accessoryUploadsTable" deletefile_routename="delete/accessoryfile"
data-search="true" :object="$accessory" />
data-pagination="true"
data-side-pagination="client"
data-show-columns="true"
data-show-export="true"
data-show-footer="true"
data-toolbar="#upload-toolbar"
data-show-refresh="true"
data-sort-order="asc"
data-sort-name="name"
class="table table-striped snipe-table"
data-export-options='{
"fileName": "export-accessories-uploads-{{ str_slug($accessory->name) }}-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","delete","download","icon"]
}'>
<thead>
<tr>
<th data-visible="true" data-field="icon" data-sortable="true">{{trans('general.file_type')}}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="image">{{ trans('general.image') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="filename" data-sortable="true">{{ trans('general.file_name') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="filesize">{{ trans('general.filesize') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="notes" data-sortable="true">{{ trans('general.notes') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="download">{{ trans('general.download') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_at" data-sortable="true">{{ trans('general.created_at') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="actions">{{ trans('table.actions') }}</th>
</tr>
</thead>
<tbody>
@if ($accessory->uploads->count() > 0)
@foreach ($accessory->uploads as $file)
<tr>
<td>
<x-icon type="paperclip" class="fa-2x" />
<i class="{{ Helper::filetype_icon($file->filename) }} icon-med" aria-hidden="true"></i>
<span class="sr-only">{{ Helper::filetype_icon($file->filename) }}</span>
</td>
<td>
@if ($file->filename)
@if ( Helper::checkUploadIsImage($file->get_src('accessories')))
<a href="{{ route('show.accessoryfile', ['accessoryId' => $accessory->id, 'fileId' => $file->id, 'download' => 'false']) }}" data-toggle="lightbox" data-type="image"><img src="{{ route('show.accessoryfile', ['accessoryId' => $accessory->id, 'fileId' => $file->id]) }}" class="img-thumbnail" style="max-width: 50px;"></a>
@endif
@endif
</td>
<td>
{{ $file->filename }}
</td>
<td data-value="{{ (Storage::exists('private_uploads/accessories/'.$file->filename) ? Storage::size('private_uploads/accessories/'.$file->filename) : '') }}">
{{ @Helper::formatFilesizeUnits(Storage::exists('private_uploads/accessories/'.$file->filename) ? Storage::size('private_uploads/accessories/'.$file->filename) : '') }}
</td>
<td>
@if ($file->note)
{{ $file->note }}
@endif
</td>
<td style="white-space: nowrap;">
@if ($file->filename)
<a href="{{ route('show.accessoryfile', [$accessory->id, $file->id]) }}" class="btn btn-sm btn-default">
<i class="fas fa-download" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.download') }}</span>
</a>
<a href="{{ route('show.accessoryfile', [$accessory->id, $file->id, 'inline' => 'true']) }}" class="btn btn-sm btn-default" target="_blank">
<x-icon type="external-link" />
</a>
@endif
</td>
<td>{{ $file->created_at }}</td>
<td>
<a class="btn delete-asset btn-danger btn-sm" href="{{ route('delete/accessoryfile', [$accessory->id, $file->id]) }}" data-content="{{ trans('general.delete_confirm', ['item' => $file->filename]) }}" data-title="{{ trans('general.delete') }}">
<i class="fas fa-trash icon-white" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.delete') }}</span>
</a>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="8">{{ trans('general.no_results') }}</td>
</tr>
@endif
</tbody>
</table>
</div>
</div> </div>
</div> </div>
</div> <!-- /.tab-pane --> </div> <!-- /.tab-pane -->

View file

@ -0,0 +1,142 @@
<!-- begin redirect submit options -->
@props([
'filepath',
'object',
'showfile_routename',
'deletefile_routename',
])
<!-- begin non-ajaxed file listing table -->
<div class="table-responsive">
<table
data-cookie-id-table="{{ str_slug($object->name) }}UploadsTable"
data-id-table="{{ str_slug($object->name) }}UploadsTable"
id="{{ str_slug($object->name) }}}UploadsTable"
data-search="true"
data-pagination="true"
data-side-pagination="client"
data-show-columns="true"
data-show-fullscreen="true"
data-show-export="true"
data-show-footer="true"
data-toolbar="#upload-toolbar"
data-show-refresh="true"
data-sort-order="asc"
data-sort-name="name"
class="table table-striped snipe-table"
data-export-options='{
"fileName": "export-license-uploads-{{ str_slug($object->name) }}-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","delete","download","icon"]
}'>
<thead>
<tr>
<th data-visible="false" data-field="id" data-sortable="true">
{{trans('general.id')}}
</th>
<th data-visible="true" data-field="type" data-sortable="true">
{{trans('general.file_type')}}
</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="image">
{{ trans('general.image') }}
</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="filename" data-sortable="true">
{{ trans('general.file_name') }}
</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="filesize">
{{ trans('general.filesize') }}
</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="notes" data-sortable="true">
{{ trans('general.notes') }}
</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="download">
{{ trans('general.download') }}
</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_at" data-sortable="true">
{{ trans('general.created_at') }}
</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_by" data-sortable="true">
{{ trans('general.created_by') }}
</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="actions">
{{ trans('table.actions') }}
</th>
</tr>
</thead>
<tbody>
@foreach ($object->uploads as $file)
<tr>
<td>
{{ $file->id }}
</td>
<td data-sort-value="{{ pathinfo($filepath.$file->filename, PATHINFO_EXTENSION) }}">
@if (Storage::exists($filepath.$file->filename))
<span class="sr-only">{{ pathinfo($filepath.$file->filename, PATHINFO_EXTENSION) }}</span>
<i class="{{ Helper::filetype_icon($file->filename) }} icon-med" aria-hidden="true" data-tooltip="true" data-title="{{ pathinfo($filepath.$file->filename, PATHINFO_EXTENSION) }}"></i>
@endif
</td>
<td>
@if (($file->filename) && (Storage::exists($filepath.$file->filename)))
@if (Helper::checkUploadIsImage($file->get_src(str_plural(strtolower(class_basename(get_class($object)))))))
<a href="{{ route($showfile_routename, [$object->id, $file->id, 'inline' => 'true']) }}" data-toggle="lightbox" data-type="image">
<img src="{{ route($showfile_routename, [$object->id, $file->id, 'inline' => 'true']) }}" class="img-thumbnail" style="max-width: 50px;">
</a>
@else
{{ trans('general.preview_not_available') }}
@endif
@else
<x-icon type="x" class="text-danger" />
{{ trans('general.file_not_found') }}
@endif
</td>
<td>
{{ $file->filename }}
</td>
<td data-value="{{ (Storage::exists($filepath.$file->filename)) ? Storage::size($filepath.$file->filename) : '' }}">
{{ (Storage::exists($filepath.$file->filename)) ? Helper::formatFilesizeUnits(Storage::size($filepath.$file->filename)) : '' }}
</td>
<td>
@if ($file->note)
{{ $file->note }}
@endif
</td>
<td style="white-space: nowrap;">
@if ($file->filename)
@if (Storage::exists($filepath.$file->filename))
<a href="{{ route($showfile_routename, [$object->id, $file->id]) }}" class="btn btn-sm btn-default">
<x-icon type="download" />
<span class="sr-only">
{{ trans('general.download') }}
</span>
</a>
<a href="{{ StorageHelper::allowSafeInline($filepath.$file->filename) ? route($showfile_routename, [$object->id, $file->id, 'inline' => 'true']) : '#' }}" class="btn btn-sm btn-default{{ StorageHelper::allowSafeInline($filepath.$file->filename) ? '' : ' disabled' }}" target="_blank">
<x-icon type="external-link" />
</a>
@endif
@endif
</td>
<td>
{{ $file->created_at }}
</td>
<td>
{{ $file->created_by }}
</td>
<td>
<a class="btn delete-asset btn-danger btn-sm hidden-print" href="{{ route($deletefile_routename, [$object->id, $file->id]) }}" data-content="Are you sure you wish to delete this file?" data-title="{{ trans('general.delete') }} {{ $file->filename }}?">
<x-icon type="delete" />
<span class="sr-only">{{ trans('general.delete') }}</span>
</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<!-- end non-ajaxed file listing table -->

View file

@ -140,96 +140,14 @@
@can('components.files', $component) @can('components.files', $component)
<div class="tab-pane" id="files"> <div class="tab-pane" id="files">
<div class="row">
<div class="table-responsive"> <div class="col-md-12">
<table <x-filestable
data-cookie-id-table="componentUploadsTable" filepath="private_uploads/components/"
data-id-table="componentUploadsTable" showfile_routename="show.componentfile"
id="componentUploadsTable" deletefile_routename="delete/componentfile"
data-search="true" :object="$component" />
data-pagination="true" </div>
data-side-pagination="client"
data-show-columns="true"
data-show-export="true"
data-show-footer="true"
data-toolbar="#upload-toolbar"
data-show-refresh="true"
data-sort-order="asc"
data-sort-name="name"
class="table table-striped snipe-table"
data-export-options='{
"fileName": "export-components-uploads-{{ str_slug($component->name) }}-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","delete","download","icon"]
}'>
<thead>
<tr>
<th data-visible="true" data-field="icon" data-sortable="true">{{trans('general.file_type')}}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="image">{{ trans('general.image') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="filename" data-sortable="true">{{ trans('general.file_name') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="filesize">{{ trans('general.filesize') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="notes" data-sortable="true">{{ trans('general.notes') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="download">{{ trans('general.download') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_at" data-sortable="true">{{ trans('general.created_at') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="actions">{{ trans('table.actions') }}</th>
</tr>
</thead>
<tbody>
@if ($component->uploads->count() > 0)
@foreach ($component->uploads as $file)
<tr>
<td>
<i class="{{ Helper::filetype_icon($file->filename) }} icon-med" aria-hidden="true"></i>
<span class="sr-only">{{ Helper::filetype_icon($file->filename) }}</span>
</td>
<td>
@if ($file->filename)
@if ( Helper::checkUploadIsImage($file->get_src('components')))
<a href="{{ route('show.componentfile', ['componentId' => $component->id, 'fileId' => $file->id, 'download' => 'false']) }}" data-toggle="lightbox" data-type="image"><img src="{{ route('show.componentfile', ['componentId' => $component->id, 'fileId' => $file->id]) }}" class="img-thumbnail" style="max-width: 50px;"></a>
@endif
@endif
</td>
<td>
{{ $file->filename }}
</td>
<td data-value="{{ (Storage::exists('private_uploads/components/'.$file->filename) ? Storage::size('private_uploads/components/'.$file->filename) : '') }}">
{{ @Helper::formatFilesizeUnits(Storage::exists('private_uploads/components/'.$file->filename) ? Storage::size('private_uploads/components/'.$file->filename) : '') }}
</td>
<td>
@if ($file->note)
{{ $file->note }}
@endif
</td>
<td>
@if ($file->filename)
<nobr><a href="{{ route('show.componentfile', [$component->id, $file->id]) }}" class="btn btn-sm btn-default">
<x-icon type="download" />
<span class="sr-only">{{ trans('general.download') }}</span>
</a>
<a href="{{ route('show.componentfile', [$component->id, $file->id, 'inline' => 'true']) }}" class="btn btn-sm btn-default" target="_blank">
<x-icon type="external-link" />
</a>
</nobr>
@endif
</td>
<td>{{ $file->created_at }}</td>
<td>
<a class="btn delete-asset btn-danger btn-sm" href="{{ route('delete/componentfile', [$component->id, $file->id]) }}" data-content="{{ trans('general.delete_confirm', ['item' => $file->filename]) }}" data-title="{{ trans('general.delete') }}">
<i class="fas fa-trash icon-white" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.delete') }}</span>
</a>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="8">{{ trans('general.no_results') }}</td>
</tr>
@endif
</tbody>
</table>
</div> </div>
</div> <!-- /.tab-pane --> </div> <!-- /.tab-pane -->
@endcan @endcan

View file

@ -428,102 +428,18 @@
<div class="tab-pane" id="files"> <div class="tab-pane" id="files">
<div class="row"> <div class="row">
<div class="col-md-12">
<x-filestable
filepath="private_uploads/consumables/"
showfile_routename="show.consumablefile"
deletefile_routename="delete/consumablefile"
:object="$consumable" />
<div class="col-md-12 col-sm-12">
<div class="table-responsive">
<table
data-cookie-id-table="consumableUploadsTable"
data-id-table="consumableUploadsTable"
id="consumableUploadsTable"
data-search="true"
data-pagination="true"
data-side-pagination="client"
data-show-columns="true"
data-show-export="true"
data-show-footer="true"
data-toolbar="#upload-toolbar"
data-show-refresh="true"
data-sort-order="asc"
data-sort-name="name"
class="table table-striped snipe-table"
data-export-options='{
"fileName": "export-consumables-uploads-{{ str_slug($consumable->name) }}-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","delete","download","icon"]
}'>
<thead>
<tr>
<th data-visible="true" data-field="icon" data-sortable="true">{{trans('general.file_type')}}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="image">{{ trans('general.image') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="filename" data-sortable="true">{{ trans('general.file_name') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="filesize">{{ trans('general.filesize') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="notes" data-sortable="true">{{ trans('general.notes') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="download">{{ trans('general.download') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_at" data-sortable="true">{{ trans('general.created_at') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="actions">{{ trans('table.actions') }}</th>
</tr>
</thead>
<tbody>
@if ($consumable->uploads->count() > 0)
@foreach ($consumable->uploads as $file)
<tr>
<td>
<i class="{{ Helper::filetype_icon($file->filename) }} icon-med" aria-hidden="true"></i>
<span class="sr-only">{{ Helper::filetype_icon($file->filename) }}</span>
</td>
<td>
@if ($file->filename)
@if ( Helper::checkUploadIsImage($file->get_src('consumables')))
<a href="{{ route('show.consumablefile', ['consumableId' => $consumable->id, 'fileId' => $file->id, 'download' => 'false']) }}" data-toggle="lightbox" data-type="image"><img src="{{ route('show.consumablefile', ['consumableId' => $consumable->id, 'fileId' => $file->id]) }}" class="img-thumbnail" style="max-width: 50px;"></a>
@endif
@endif
</td>
<td>
{{ $file->filename }}
</td>
<td data-value="{{ (Storage::exists('private_uploads/consumables/'.$file->filename) ? Storage::size('private_uploads/consumables/'.$file->filename) : '') }}">
{{ @Helper::formatFilesizeUnits(Storage::exists('private_uploads/consumables/'.$file->filename) ? Storage::size('private_uploads/consumables/'.$file->filename) : '') }}
</td>
<td>
@if ($file->note)
{!! nl2br(Helper::parseEscapedMarkedownInline($file->note)) !!}
@endif
</td>
<td>
@if ($file->filename)
<a href="{{ route('show.consumablefile', [$consumable->id, $file->id]) }}" class="btn btn-sm btn-default">
<i class="fas fa-download" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.download') }}</span>
</a>
<a href="{{ route('show.consumablefile', [$consumable->id, $file->id, 'inline' => 'true']) }}" class="btn btn-sm btn-default" target="_blank">
<x-icon type="external-link" />
</a>
@endif
</td>
<td>{{ $file->created_at }}</td>
<td>
<a class="btn delete-asset btn-danger btn-sm" href="{{ route('delete/consumablefile', [$consumable->id, $file->id]) }}" data-content="{{ trans('general.delete_confirm', ['item' => $file->filename]) }}" data-title="{{ trans('general.delete') }}">
<i class="fas fa-trash icon-white" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.delete') }}</span>
</a>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="8">{{ trans('general.no_results') }}</td>
</tr>
@endif
</tbody>
</table>
</div> </div>
</div> </div>
</div> <!--/ROW-->
</div><!--/FILES--> </div><!--/FILES-->
<div class="tab-pane" id="history"> <div class="tab-pane" id="history">

View file

@ -34,7 +34,7 @@
<div class="dashboard small-box bg-teal"> <div class="dashboard small-box bg-teal">
<div class="inner"> <div class="inner">
<h3>{{ number_format(\App\Models\Asset::AssetsForShow()->count()) }}</h3> <h3>{{ number_format(\App\Models\Asset::AssetsForShow()->count()) }}</h3>
<p>{{ strtolower(trans('general.assets')) }}</p> <p>{{ trans('general.assets') }}</p>
</div> </div>
<div class="icon" aria-hidden="true"> <div class="icon" aria-hidden="true">
<x-icon type="assets" /> <x-icon type="assets" />
@ -54,7 +54,7 @@
<div class="dashboard small-box bg-maroon"> <div class="dashboard small-box bg-maroon">
<div class="inner"> <div class="inner">
<h3>{{ number_format($counts['license']) }}</h3> <h3>{{ number_format($counts['license']) }}</h3>
<p>{{ strtolower(trans('general.licenses')) }}</p> <p>{{ trans('general.licenses') }}</p>
</div> </div>
<div class="icon" aria-hidden="true"> <div class="icon" aria-hidden="true">
<x-icon type="licenses" /> <x-icon type="licenses" />
@ -75,7 +75,7 @@
<div class="dashboard small-box bg-orange"> <div class="dashboard small-box bg-orange">
<div class="inner"> <div class="inner">
<h3> {{ number_format($counts['accessory']) }}</h3> <h3> {{ number_format($counts['accessory']) }}</h3>
<p>{{ strtolower(trans('general.accessories')) }}</p> <p>{{ trans('general.accessories') }}</p>
</div> </div>
<div class="icon" aria-hidden="true"> <div class="icon" aria-hidden="true">
<x-icon type="accessories" /> <x-icon type="accessories" />
@ -96,7 +96,7 @@
<div class="dashboard small-box bg-purple"> <div class="dashboard small-box bg-purple">
<div class="inner"> <div class="inner">
<h3> {{ number_format($counts['consumable']) }}</h3> <h3> {{ number_format($counts['consumable']) }}</h3>
<p>{{ strtolower(trans('general.consumables')) }}</p> <p>{{ trans('general.consumables') }}</p>
</div> </div>
<div class="icon" aria-hidden="true"> <div class="icon" aria-hidden="true">
<x-icon type="consumables" /> <x-icon type="consumables" />
@ -115,7 +115,7 @@
<div class="dashboard small-box bg-yellow"> <div class="dashboard small-box bg-yellow">
<div class="inner"> <div class="inner">
<h3>{{ number_format($counts['component']) }}</h3> <h3>{{ number_format($counts['component']) }}</h3>
<p>{{ strtolower(trans('general.components')) }}</p> <p>{{ trans('general.components') }}</p>
</div> </div>
<div class="icon" aria-hidden="true"> <div class="icon" aria-hidden="true">
<x-icon type="components" /> <x-icon type="components" />
@ -135,7 +135,7 @@
<div class="dashboard small-box bg-light-blue"> <div class="dashboard small-box bg-light-blue">
<div class="inner"> <div class="inner">
<h3>{{ number_format($counts['user']) }}</h3> <h3>{{ number_format($counts['user']) }}</h3>
<p>{{ strtolower(trans('general.people')) }}</p> <p>{{ trans('general.people') }}</p>
</div> </div>
<div class="icon" aria-hidden="true"> <div class="icon" aria-hidden="true">
<x-icon type="users" /> <x-icon type="users" />

View file

@ -115,5 +115,12 @@
@section('moar_scripts') @section('moar_scripts')
@include('partials/assets-assigned') @include('partials/assets-assigned')
<script nonce="{{ csrf_token() }}">
$(function () {
//if there's already a user selected, make sure their checked-out assets show up
// (if there isn't one, it won't do anything)
$('#assigned_user').change();
});
</script>
@stop @stop

View file

@ -1362,102 +1362,11 @@
<div class="tab-pane fade" id="files"> <div class="tab-pane fade" id="files">
<div class="row{{ ($asset->uploads->count() > 0 ) ? '' : ' hidden-print' }}"> <div class="row{{ ($asset->uploads->count() > 0 ) ? '' : ' hidden-print' }}">
<div class="col-md-12"> <div class="col-md-12">
<x-filestable
@if ($asset->uploads->count() > 0) filepath="private_uploads/assets/"
<table showfile_routename="show/assetfile"
class="table table-striped snipe-table" deletefile_routename="delete/modelfile"
id="assetFileHistory" :object="$asset" />
data-pagination="true"
data-id-table="assetFileHistory"
data-search="true"
data-side-pagination="client"
data-sortable="true"
data-show-columns="true"
data-show-fullscreen="true"
data-show-refresh="true"
data-sort-order="desc"
data-sort-name="created_at"
data-show-export="true"
data-export-options='{
"fileName": "export-asset-{{ $asset->id }}-files",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'
data-cookie-id-table="assetFileHistory">
<thead>
<tr>
<th data-visible="true" data-field="icon" data-sortable="true">{{trans('general.file_type')}}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="image">{{ trans('general.image') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="filename" data-sortable="true">{{ trans('general.file_name') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="filesize">{{ trans('general.filesize') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="notes" data-sortable="true">{{ trans('general.notes') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="download">{{ trans('general.download') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_at" data-sortable="true">{{ trans('general.created_at') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="actions">{{ trans('table.actions') }}</th>
</tr>
</thead>
<tbody>
@foreach ($asset->uploads as $file)
<tr>
<td><i class="{{ Helper::filetype_icon($file->filename) }} icon-med" aria-hidden="true"></i></td>
<td>
@if ( Helper::checkUploadIsImage($file->get_src('assets')))
<a href="{{ route('show/assetfile', ['assetId' => $asset->id, 'fileId' =>$file->id]) }}" data-toggle="lightbox" data-type="image" data-title="{{ $file->filename }}" data-footer="{{ Helper::getFormattedDateObject($asset->last_checkout, 'datetime', false) }}">
<img src="{{ route('show/assetfile', ['assetId' => $asset->id, 'fileId' =>$file->id]) }}" style="max-width: 50px;">
</a>
@endif
</td>
<td>
@if (Storage::exists('private_uploads/assets/'.$file->filename))
{{ $file->filename }}
@else
<del>{{ $file->filename }}</del>
@endif
</td>
<td data-value="{{ (Storage::exists('private_uploads/assets/'.$file->filename) ? Storage::size('private_uploads/assets/'.$file->filename) : '') }}">
{{ @Helper::formatFilesizeUnits(Storage::exists('private_uploads/assets/'.$file->filename) ? Storage::size('private_uploads/assets/'.$file->filename) : '') }}
</td>
<td>
@if ($file->note)
{{ $file->note }}
@endif
</td>
<td>
@if (($file->filename) && (Storage::exists('private_uploads/assets/'.$file->filename)))
<a href="{{ route('show/assetfile', [$asset->id, $file->id, 'download'=>'true']) }}" class="btn btn-sm btn-default hidden-print">
<x-icon type="download" />
</a>
<a href="{{ route('show/assetfile', [$asset->id, $file->id, 'inline'=>'true']) }}" class="btn btn-sm btn-default hidden-print" target="_blank">
<x-icon type="external-link" />
</a>
@endif
</td>
<td>
@if ($file->created_at)
{{ Helper::getFormattedDateObject($file->created_at, 'datetime', false) }}
@endif
</td>
<td>
@can('update', \App\Models\Asset::class)
<a class="btn delete-asset btn-sm btn-danger btn-sm hidden-print" href="{{ route('delete/assetfile', [$asset->id, $file->id]) }}" data-tooltip="true" data-title="Delete" data-content="{{ trans('general.delete_confirm', ['item' => $file->filename]) }}">
<x-icon type="delete" />
</a>
@endcan
</td>
</tr>
@endforeach
</tbody>
</table>
@else
<div class="alert alert-info alert-block hidden-print">
<x-icon type="info-circle" />
{{ trans('general.no_results') }}
</div>
@endif
</div> <!-- /.col-md-12 --> </div> <!-- /.col-md-12 -->
</div> <!-- /.row --> </div> <!-- /.row -->
</div> <!-- /.tab-pane files --> </div> <!-- /.tab-pane files -->
@ -1467,101 +1376,11 @@
<div class="row{{ (($asset->model) && ($asset->model->uploads->count() > 0)) ? '' : ' hidden-print' }}"> <div class="row{{ (($asset->model) && ($asset->model->uploads->count() > 0)) ? '' : ' hidden-print' }}">
<div class="col-md-12"> <div class="col-md-12">
@if (($asset->model) && ($asset->model->uploads->count() > 0)) <x-filestable
<table filepath="private_uploads/assetmodels/"
class="table table-striped snipe-table" showfile_routename="show/modelfile"
id="assetModelFileHistory" deletefile_routename="userfile.destroy"
data-pagination="true" :object="$asset->model" />
data-id-table="assetModelFileHistory"
data-search="true"
data-side-pagination="client"
data-sortable="true"
data-show-columns="true"
data-show-fullscreen="true"
data-show-refresh="true"
data-sort-order="desc"
data-sort-name="created_at"
data-show-export="true"
data-export-options='{
"fileName": "export-assetmodel-{{ $asset->model->id }}-files",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'
data-cookie-id-table="assetFileHistory">
<thead>
<tr>
<th data-visible="true" data-field="icon" data-sortable="true">{{trans('general.file_type')}}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="image">{{ trans('general.image') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="filename" data-sortable="true">{{ trans('general.file_name') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="filesize">{{ trans('general.filesize') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="notes" data-sortable="true">{{ trans('general.notes') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="download">{{ trans('general.download') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_at" data-sortable="true">{{ trans('general.created_at') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="actions">{{ trans('table.actions') }}</th>
</tr>
</thead>
<tbody>
@foreach ($asset->model->uploads as $file)
<tr>
<td><i class="{{ Helper::filetype_icon($file->filename) }} icon-med" aria-hidden="true"></i></td>
<td>
@if ( Helper::checkUploadIsImage($file->get_src('assetmodels')))
<a href="{{ route('show/modelfile', ['modelID' => $asset->model->id, 'fileId' =>$file->id]) }}" data-toggle="lightbox" data-type="image" data-title="{{ $file->filename }}" data-footer="{{ Helper::getFormattedDateObject($asset->last_checkout, 'datetime', false) }}">
<img src="{{ route('show/modelfile', ['modelID' => $asset->model->id, 'fileId' =>$file->id]) }}" style="max-width: 50px;">
</a>
@endif
</td>
<td>
@if (Storage::exists('private_uploads/assetmodels/'.$file->filename))
{{ $file->filename }}
@else
<del>{{ $file->filename }}</del>
@endif
</td>
<td data-value="{{ (Storage::exists('private_uploads/assetmodels/'.$file->filename)) ? Storage::size('private_uploads/assetmodels/'.$file->filename) : '' }}">
{{ (Storage::exists('private_uploads/assetmodels/'.$file->filename)) ? Helper::formatFilesizeUnits(Storage::size('private_uploads/assetmodels/'.$file->filename)) : '' }}
</td>
<td>
@if ($file->note)
{{ $file->note }}
@endif
</td>
<td>
@if (($file->filename) && (Storage::exists('private_uploads/assetmodels/'.$file->filename)))
<a href="{{ route('show/modelfile', [$asset->model->id, $file->id]) }}" class="btn btn-sm btn-default hidden-print">
<x-icon type="download" class="hidden-print" />
</a>
<a href="{{ route('show/modelfile', [$asset->model->id, $file->id, 'inline'=>'true']) }}" class="btn btn-sm btn-default hidden-print" target="_blank">
<x-icon type="external-link" class="hidden-print" />
</a>
@endif
</td>
<td>
@if ($file->created_at)
{{ Helper::getFormattedDateObject($file->created_at, 'datetime', false) }}
@endif
</td>
<td>
@can('update', \App\Models\AssetModel::class)
<a class="btn delete-asset btn-sm btn-danger btn-sm hidden-print" href="{{ route('delete/modelfile', [$asset->model->id, $file->id]) }}" data-tooltip="true" data-title="Delete" data-content="{{ trans('general.delete_confirm', ['item' => $file->filename]) }}">
<x-icon type="delete" class="hidden-print"/></i>
</a>
@endcan
</td>
</tr>
@endforeach
</tbody>
</table>
@else
<div class="alert alert-info alert-block">
<x-icon type="info-circle" />
{{ trans('general.no_results') }}
</div>
@endif
</div> <!-- /.col-md-12 --> </div> <!-- /.col-md-12 -->
</div> <!-- /.row --> </div> <!-- /.row -->

View file

@ -464,100 +464,13 @@
@can('licenses.files', $license) @can('licenses.files', $license)
<div class="tab-pane" id="files"> <div class="tab-pane" id="files">
<div class="table-responsive">
<table
data-cookie-id-table="licenseUploadsTable"
data-id-table="licenseUploadsTable"
id="licenseUploadsTable"
data-search="true"
data-pagination="true"
data-side-pagination="client"
data-show-columns="true"
data-show-export="true"
data-show-footer="true"
data-toolbar="#upload-toolbar"
data-show-refresh="true"
data-sort-order="asc"
data-sort-name="name"
class="table table-striped snipe-table"
data-export-options='{
"fileName": "export-license-uploads-{{ str_slug($license->name) }}-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","delete","download","icon"]
}'>
<thead>
<tr>
<th data-visible="true" data-field="icon" data-sortable="true">{{trans('general.file_type')}}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="image">{{ trans('general.image') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="filename" data-sortable="true">{{ trans('general.file_name') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="filesize">{{ trans('general.filesize') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="notes" data-sortable="true">{{ trans('general.notes') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="download">{{ trans('general.download') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_at" data-sortable="true">{{ trans('general.created_at') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="actions">{{ trans('table.actions') }}</th>
</tr>
</thead>
<tbody>
@if ($license->uploads->count() > 0)
@foreach ($license->uploads as $file)
<tr>
<td>
<i class="{{ Helper::filetype_icon($file->filename) }} icon-med" aria-hidden="true"></i>
<span class="sr-only">{{ Helper::filetype_icon($file->filename) }}</span>
</td> <x-filestable
<td> filepath="private_uploads/licenses/"
@if ($file->filename) showfile_routename="show.licensefile"
@if ((Storage::exists('private_uploads/licenses/'.$file->filename)) && ( Helper::checkUploadIsImage($file->get_src('licenses')))) deletefile_routename="delete/licensefile"
<a href="{{ route('show.licensefile', ['licenseId' => $license->id, 'fileId' => $file->id, 'download' => 'false']) }}" data-toggle="lightbox" data-type="image"><img src="{{ route('show.licensefile', ['licenseId' => $license->id, 'fileId' => $file->id]) }}" class="img-thumbnail" style="max-width: 50px;"></a> :object="$license" />
@endif
@endif
</td>
<td>
@if (Storage::exists('private_uploads/licenses/'.$file->filename))
{{ $file->filename }}
@else
<del>{{ $file->filename }}</del>
@endif
</td>
<td data-value="{{ (Storage::exists('private_uploads/licenses/'.$file->filename)) ? Storage::size('private_uploads/licenses/'.$file->filename) : '' }}">
{{ (Storage::exists('private_uploads/licenses/'.$file->filename)) ? Helper::formatFilesizeUnits(Storage::size('private_uploads/licenses/'.$file->filename)) : '' }}
</td>
<td>
@if ($file->note)
{{ $file->note }}
@endif
</td>
<td>
@if ($file->filename)
<a href="{{ route('show.licensefile', [$license->id, $file->id]) }}" class="btn btn-sm btn-default">
<x-icon type="download"/>
<span class="sr-only">{{ trans('general.download') }}</span>
</a>
<a href="{{ route('show.licensefile', [$license->id, $file->id, 'inline' => 'true']) }}" class="btn btn-sm btn-default" target="_blank">
<x-icon type="external-link" />
</a>
@endif
</td>
<td>{{ $file->created_at }}</td>
<td>
<a class="btn delete-asset btn-danger btn-sm" href="{{ route('delete/licensefile', [$license->id, $file->id]) }}" data-content="{{ trans('general.delete_confirm', ['item' => $file->filename]) }}" data-title="{{ trans('general.delete') }}">
<x-icon type="delete" />
<span class="sr-only">{{ trans('general.delete') }}</span>
</a>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="8">{{ trans('general.no_results') }}</td>
</tr>
@endif
</tbody>
</table>
</div>
</div> <!-- /.tab-pane --> </div> <!-- /.tab-pane -->
@endcan @endcan

View file

@ -107,99 +107,11 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@if ($model->uploads->count() > 0) <x-filestable
<table filepath="private_uploads/assetmodels/"
class="table table-striped snipe-table" showfile_routename="show/modelfile"
id="modelFileHistory" deletefile_routename="delete/modelfile"
data-pagination="true" :object="$model" />
data-id-table="modelFileHistory"
data-search="true"
data-side-pagination="client"
data-sortable="true"
data-show-columns="true"
data-show-fullscreen="true"
data-show-refresh="true"
data-sort-order="desc"
data-sort-name="created_at"
data-show-export="true"
data-export-options='{
"fileName": "export-asset-{{ $model->id }}-files",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'
data-cookie-id-table="assetFileHistory">
<thead>
<tr>
<th data-visible="true" data-field="icon" data-sortable="true">{{trans('general.file_type')}}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="image">{{ trans('general.image') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="filename" data-sortable="true">{{ trans('general.file_name') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="filesize">{{ trans('general.filesize') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="notes" data-sortable="true">{{ trans('general.notes') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="download">{{ trans('general.download') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_at" data-sortable="true">{{ trans('general.created_at') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="actions">{{ trans('table.actions') }}</th>
</tr>
</thead>
<tbody>
@foreach ($model->uploads as $file)
<tr>
<td><i class="{{ Helper::filetype_icon($file->filename) }} icon-med" aria-hidden="true"></i></td>
<td>
@if ((Storage::exists('private_uploads/assetmodels/'.$file->filename)) && ( Helper::checkUploadIsImage($file->get_src('assetmodels'))))
<a href="{{ route('show/modelfile', ['modelID' => $model->id, 'fileId' => $file->id]) }}" data-toggle="lightbox" data-type="image" data-title="{{ $file->filename }}">
<img src="{{ route('show/modelfile', ['modelID' => $model->id, 'fileId' =>$file->id]) }}" style="max-width: 50px;">
</a>
@endif
</td>
<td>
@if (Storage::exists('private_uploads/assetmodels/'.$file->filename))
{{ $file->filename }}
@else
<del>{{ $file->filename }}</del>
@endif
</td>
<td data-value="{{ (Storage::exists('private_uploads/assetmodels/'.$file->filename)) ? Storage::size('private_uploads/assetmodels/'.$file->filename) : '' }}">
{{ (Storage::exists('private_uploads/assetmodels/'.$file->filename)) ? Helper::formatFilesizeUnits(Storage::size('private_uploads/assetmodels/'.$file->filename)) : '' }}
</td>
<td>
@if ($file->note)
{{ $file->note }}
@endif
</td>
<td style="white-space: nowrap">
@if (($file->filename) && (Storage::exists('private_uploads/assetmodels/'.$file->filename)))
<a href="{{ route('show/modelfile', [$model->id, $file->id]) }}" class="btn btn-sm btn-default">
<i class="fas fa-download" aria-hidden="true"></i>
</a>
<a href="{{ route('show/modelfile', [$model->id, $file->id, 'inline'=>'true']) }}" class="btn btn-sm btn-default" target="_blank">
<x-icon type="external-link" />
</a>
@endif
</td>
<td>
@if ($file->created_at)
{{ Helper::getFormattedDateObject($file->created_at, 'datetime', false) }}
@endif
</td>
<td>
@can('update', \App\Models\AssetModel::class)
<a class="btn delete-asset btn-sm btn-danger btn-sm" href="{{ route('delete/assetfile', [$model->id, $file->id]) }}" data-tooltip="true" data-title="Delete" data-content="{{ trans('general.delete_confirm', ['item' => $file->filename]) }}"><i class="fas fa-trash icon-white" aria-hidden="true"></i></a>
@endcan
</td>
</tr>
@endforeach
</tbody>
</table>
@else
<div class="alert alert-info alert-block">
<i class="fas fa-info-circle"></i>
{{ trans('general.no_results') }}
</div>
@endif
</div> <!-- /.col-md-12 --> </div> <!-- /.col-md-12 -->
</div> <!-- /.row --> </div> <!-- /.row -->

View file

@ -23,6 +23,9 @@
@can('update', \App\Models\Asset::class) @can('update', \App\Models\Asset::class)
<option value="edit">{{ trans('button.edit') }}</option> <option value="edit">{{ trans('button.edit') }}</option>
@endcan @endcan
@can('checkout', \App\Models\Asset::class)
<option value="checkout">{{ trans('general.bulk_checkout') }}</option>
@endcan
@can('delete', \App\Models\Asset::class) @can('delete', \App\Models\Asset::class)
<option value="delete">{{ trans('button.delete') }}</option> <option value="delete">{{ trans('button.delete') }}</option>
@endcan @endcan

View file

@ -971,102 +971,11 @@
<div class="row"> <div class="row">
<div class="col-md-12 col-sm-12"> <div class="col-md-12 col-sm-12">
<div class="table-responsive"> <x-filestable
<table filepath="private_uploads/users/"
data-cookie-id-table="userUploadsTable" showfile_routename="show/userfile"
data-id-table="userUploadsTable" deletefile_routename="userfile.destroy"
id="userUploadsTable" :object="$user" />
data-search="true"
data-pagination="true"
data-side-pagination="client"
data-show-columns="true"
data-show-fullscreen="true"
data-show-export="true"
data-show-footer="true"
data-toolbar="#upload-toolbar"
data-show-refresh="true"
data-sort-order="asc"
data-sort-name="name"
class="table table-striped snipe-table"
data-export-options='{
"fileName": "export-license-uploads-{{ str_slug($user->name) }}-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","delete","download","icon"]
}'>
<thead>
<tr>
<th data-visible="true" data-field="icon" data-sortable="true">{{trans('general.file_type')}}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="image">{{ trans('general.image') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="filename" data-sortable="true">{{ trans('general.file_name') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="filesize">{{ trans('general.filesize') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="notes" data-sortable="true">{{ trans('general.notes') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="download">{{ trans('general.download') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_at" data-sortable="true">{{ trans('general.created_at') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="actions">{{ trans('table.actions') }}</th>
</tr>
</thead>
<tbody>
@foreach ($user->uploads as $file)
<tr>
<td>
<i class="{{ Helper::filetype_icon($file->filename) }} icon-med" aria-hidden="true"></i>
<span class="sr-only">{{ Helper::filetype_icon($file->filename) }}</span>
</td>
<td>
@if (($file->filename) && (Storage::exists('private_uploads/users/'.$file->filename)))
@if (Helper::checkUploadIsImage($file->get_src('users')))
<a href="{{ route('show/userfile', [$user->id, $file->id, 'inline' => 'true']) }}" data-toggle="lightbox" data-type="image"><img src="{{ route('show/userfile', [$user->id, $file->id, 'inline' => 'true']) }}" class="img-thumbnail" style="max-width: 50px;"></a>
@else
{{ trans('general.preview_not_available') }}
@endif
@else
<x-icon type="x" class="text-danger" />
{{ trans('general.file_not_found') }}
@endif
</td>
<td>
{{ $file->filename }}
</td>
<td data-value="{{ (Storage::exists('private_uploads/users/'.$file->filename)) ? Storage::size('private_uploads/users/'.$file->filename) : '' }}">
{{ (Storage::exists('private_uploads/users/'.$file->filename)) ? Helper::formatFilesizeUnits(Storage::size('private_uploads/users/'.$file->filename)) : '' }}
</td>
<td>
@if ($file->note)
{{ $file->note }}
@endif
</td>
<td>
@if ($file->filename)
@if (Storage::exists('private_uploads/users/'.$file->filename))
<a href="{{ route('show/userfile', [$user->id, $file->id]) }}" class="btn btn-sm btn-default">
<x-icon type="download" />
<span class="sr-only">{{ trans('general.download') }}</span>
</a>
<a href="{{ route('show/userfile', [$user->id, $file->id, 'inline' => 'true']) }}" class="btn btn-sm btn-default" target="_blank">
<x-icon type="external-link" />
</a>
@endif
@endif
</td>
<td>{{ $file->created_at }}</td>
<td>
<a class="btn delete-asset btn-danger btn-sm hidden-print" href="{{ route('userfile.destroy', [$user->id, $file->id]) }}" data-content="Are you sure you wish to delete this file?" data-title="{{ trans('general.delete') }} {{ $file->filename }}?">
<x-icon type="delete" />
<span class="sr-only">{{ trans('general.delete') }}</span>
</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div> </div>
</div> <!--/ROW--> </div> <!--/ROW-->
</div><!--/FILES--> </div><!--/FILES-->

View file

@ -0,0 +1,20 @@
<?php
namespace Tests\Unit\BladeComponents;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Str;
use Tests\TestCase;
class IconComponentTest extends TestCase
{
public function testIconComponentDoesNotEndInNewline()
{
$renderedTemplateString = View::make('blade.icon', ['type' => 'checkout'])->render();
$this->assertFalse(
Str::endsWith($renderedTemplateString, PHP_EOL),
'Newline found at end of icon component. Bootstrap tables will not render if there is a newline at the end of the file.'
);
}
}