diff --git a/Dockerfile.alpine b/Dockerfile.alpine index a5316e1492..6d68e7ad9e 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -29,6 +29,7 @@ RUN apk add --no-cache \ php81-sodium \ php81-redis \ php81-pecl-memcached \ + php81-exif \ curl \ wget \ vim \ diff --git a/TESTING.md b/TESTING.md index 3a2f4e5385..92f3184eec 100644 --- a/TESTING.md +++ b/TESTING.md @@ -45,8 +45,21 @@ DB_PASSWORD={} Now you are ready to run the entire test suite from your terminal: -`php artisan test` +```shell +php artisan test +```` To run individual test files, you can pass the path to the test that you want to run: -`php artisan test tests/Unit/AccessoryTest.php` +```shell +php artisan test tests/Unit/AccessoryTest.php +``` + +Some tests, like ones concerning LDAP, are marked with the `@group` annotation. Those groups can be run, or excluded, using the `--group` or `--exclude-group` flags: + +```shell +php artisan test --group=ldap + +php artisan test --exclude-group=ldap +``` +This can be helpful if a set of tests are failing because you don't have an extension, like LDAP, installed. diff --git a/app/Console/Commands/RestoreFromBackup.php b/app/Console/Commands/RestoreFromBackup.php index 7108568c5c..2133b22be1 100644 --- a/app/Console/Commands/RestoreFromBackup.php +++ b/app/Console/Commands/RestoreFromBackup.php @@ -5,6 +5,151 @@ namespace App\Console\Commands; use Illuminate\Console\Command; use ZipArchive; +class SQLStreamer { + private $input; + private $output; + // embed the prefix here? + public ?string $prefix; + + private bool $reading_beginning_of_line = true; + + public static $buffer_size = 1024 * 1024; // use a 1MB buffer, ought to work fine for most cases? + + public array $tablenames = []; + private bool $should_guess = false; + private bool $statement_is_permitted = false; + + public function __construct($input, $output, string $prefix = null) + { + $this->input = $input; + $this->output = $output; + $this->prefix = $prefix; + } + + public function parse_sql(string $line): string { + // take into account the 'start of line or not' setting as an instance variable? + // 'continuation' lines for a permitted statement are PERMITTED. + if($this->statement_is_permitted && $line[0] === ' ') { + return $line; + } + + $table_regex = '`?([a-zA-Z0-9_]+)`?'; + $allowed_statements = [ + "/^(DROP TABLE (?:IF EXISTS )?)`$table_regex(.*)$/" => false, + "/^(CREATE TABLE )$table_regex(.*)$/" => true, //sets up 'continuation' + "/^(LOCK TABLES )$table_regex(.*)$/" => false, + "/^(INSERT INTO )$table_regex(.*)$/" => false, + "/^UNLOCK TABLES/" => false, + // "/^\\) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;/" => false, // FIXME not sure what to do here? + "/^\\)[a-zA-Z0-9_= ]*;$/" => false + // ^^^^^^ that bit should *exit* the 'perimitted' black + ]; + + foreach($allowed_statements as $statement => $statechange) { +// $this->info("Checking regex: $statement...\n"); + $matches = []; + if (preg_match($statement,$line,$matches)) { + $this->statement_is_permitted = $statechange; + // matches are: 1 => first part of the statement, 2 => tablename, 3 => rest of statement + // (with of course 0 being "the whole match") + if (@$matches[2]) { +// print "Found a tablename! It's: ".$matches[2]."\n"; + if ($this->should_guess) { + @$this->tablenames[$matches[2]] += 1; + continue; //oh? FIXME + } else { + $cleaned_tablename = \DB::getTablePrefix().preg_replace('/^'.$this->prefix.'/','',$matches[2]); + $line = preg_replace($statement,'$1`'.$cleaned_tablename.'`$3' , $line); + } + } else { + // no explicit tablename in this one, leave the line alone + } + //how do we *replace* the tablename? +// print "RETURNING LINE: $line"; + return $line; + } + } + // all that is not allowed is denied. + return ""; + } + + //this is used in exactly *TWO* places, and in both cases should return a prefix I think? + // first - if you do the --sanitize-only one (which is mostly for testing/development) + // next - when you run *without* a guessed prefix, this is run first to figure out the prefix + // I think we have to *duplicate* the call to be able to run it again? + public static function guess_prefix($input):string + { + $parser = new self($input, null); + $parser->should_guess = true; + $parser->line_aware_piping(); // <----- THIS is doing the heavy lifting! + + $check_tables = ['settings' => null, 'migrations' => null /* 'assets' => null */]; //TODO - move to statics? + //can't use 'users' because the 'accessories_users' table? + // can't use 'assets' because 'ver1_components_assets' + foreach($check_tables as $check_table => $_ignore) { + foreach ($parser->tablenames as $tablename => $_count) { +// print "Comparing $tablename to $check_table\n"; + if (str_ends_with($tablename,$check_table)) { +// print "Found one!\n"; + $check_tables[$check_table] = substr($tablename,0,-strlen($check_table)); + } + } + } + $guessed_prefix = null; + foreach ($check_tables as $clean_table => $prefix_guess) { + if(is_null($prefix_guess)) { + print("Couldn't find table $clean_table\n"); + die(); + } + if(is_null($guessed_prefix)) { + $guessed_prefix = $prefix_guess; + } else { + if ($guessed_prefix != $prefix_guess) { + print("Prefix mismatch! Had guessed $guessed_prefix but got $prefix_guess\n"); + die(); + } + } + } + + return $guessed_prefix; + + } + + public function line_aware_piping(): int + { + $bytes_read = 0; + if (! $this->input) { + throw new \Exception("No Input available for line_aware_piping"); + } + + while (($buffer = fgets($this->input, SQLStreamer::$buffer_size)) !== false) { + $bytes_read += strlen($buffer); + if ($this->reading_beginning_of_line) { + // \Log::debug("Buffer is: '$buffer'"); + $cleaned_buffer = $this->parse_sql($buffer); + if ($this->output) { + $bytes_written = fwrite($this->output, $cleaned_buffer); + + if ($bytes_written === false) { + throw new \Exception("Unable to write to pipe"); + } + } + } + // if we got a newline at the end of this, then the _next_ read is the beginning of a line + if($buffer[strlen($buffer)-1] === "\n") { + $this->reading_beginning_of_line = true; + } else { + $this->reading_beginning_of_line = false; + } + + } + return $bytes_read; + + } +} + + + class RestoreFromBackup extends Command { /** @@ -12,10 +157,13 @@ class RestoreFromBackup extends Command * * @var string */ + // FIXME - , stripping prefixes and nonstandard SQL statements. Without --prefix, guess and return the correct prefix to strip protected $signature = 'snipeit:restore {--force : Skip the danger prompt; assuming you enter "y"} {filename : The zip file to be migrated} - {--no-progress : Don\'t show a progress bar}'; + {--no-progress : Don\'t show a progress bar} + {--sanitize-guess-prefix : Guess and output the table-prefix needed to "sanitize" the SQL} + {--sanitize-with-prefix= : "Sanitize" the SQL, using the passed-in table prefix (can be learned from --sanitize-guess-prefix). Pass as just \'--sanitize-with-prefix=\' to use no prefix}'; /** * The console command description. @@ -34,8 +182,6 @@ class RestoreFromBackup extends Command parent::__construct(); } - public static $buffer_size = 1024 * 1024; // use a 1MB buffer, ought to work fine for most cases? - /** * Execute the console command. * @@ -55,7 +201,7 @@ class RestoreFromBackup extends Command return $this->error('Missing required filename'); } - if (! $this->option('force') && ! $this->confirm('Are you sure you wish to restore from the given backup file? This can lead to MASSIVE DATA LOSS!')) { + if (! $this->option('force') && ! $this->option('sanitize-guess-prefix') && ! $this->confirm('Are you sure you wish to restore from the given backup file? This can lead to MASSIVE DATA LOSS!')) { return $this->error('Data loss not confirmed'); } @@ -158,11 +304,11 @@ class RestoreFromBackup extends Command } foreach (array_merge($private_dirs, $public_dirs) as $dir) { - $last_pos = strrpos($raw_path, $dir.'/'); + $last_pos = strrpos($raw_path, $dir . '/'); if ($last_pos !== false) { //print("INTERESTING - last_pos is $last_pos when searching $raw_path for $dir - last_pos+strlen(\$dir) is: ".($last_pos+strlen($dir))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n"); //print("We would copy $raw_path to $dir.\n"); //FIXME append to a path? - $interesting_files[$raw_path] = ['dest' =>$dir, 'index' => $i]; + $interesting_files[$raw_path] = ['dest' => $dir, 'index' => $i]; continue 2; if ($last_pos + strlen($dir) + 1 == strlen($raw_path)) { // we don't care about that; we just want files with the appropriate prefix @@ -171,7 +317,7 @@ class RestoreFromBackup extends Command } } $good_extensions = ['png', 'gif', 'jpg', 'svg', 'jpeg', 'doc', 'docx', 'pdf', 'txt', - 'zip', 'rar', 'xls', 'xlsx', 'lic', 'xml', 'rtf', 'webp', 'key', 'ico', ]; + 'zip', 'rar', 'xls', 'xlsx', 'lic', 'xml', 'rtf', 'webp', 'key', 'ico',]; foreach (array_merge($private_files, $public_files) as $file) { $has_wildcard = (strpos($file, '*') !== false); if ($has_wildcard) { @@ -180,8 +326,8 @@ class RestoreFromBackup extends Command $last_pos = strrpos($raw_path, $file); // no trailing slash! if ($last_pos !== false) { $extension = strtolower(pathinfo($raw_path, PATHINFO_EXTENSION)); - if (! in_array($extension, $good_extensions)) { - $this->warn('Potentially unsafe file '.$raw_path.' is being skipped'); + if (!in_array($extension, $good_extensions)) { + $this->warn('Potentially unsafe file ' . $raw_path . ' is being skipped'); $boring_files[] = $raw_path; continue 2; } @@ -196,7 +342,6 @@ class RestoreFromBackup extends Command } $boring_files[] = $raw_path; //if we've gotten to here and haven't continue'ed our way into the next iteration, we don't want this file } // end of pre-processing the ZIP file for-loop - // print_r($interesting_files);exit(-1); if (count($sqlfiles) != 1) { @@ -208,6 +353,17 @@ class RestoreFromBackup extends Command //older Snipe-IT installs don't have the db-dumps subdirectory component } + $sql_stat = $za->statIndex($sqlfile_indices[0]); + //$this->info("SQL Stat is: ".print_r($sql_stat,true)); + $sql_contents = $za->getStream($sql_stat['name']); // maybe copy *THIS* thing? + + // OKAY, now that we *found* the sql file if we're doing just the guess-prefix thing, we can do that *HERE* I think? + if ($this->option('sanitize-guess-prefix')) { + $prefix = SQLStreamer::guess_prefix($sql_contents); + $this->line($prefix); + return $this->info("Re-run this command with '--sanitize-with-prefix=".$prefix."' to see an attempt to sanitze your SQL."); + } + //how to invoke the restore? $pipes = []; @@ -228,6 +384,7 @@ class RestoreFromBackup extends Command return $this->error('Unable to invoke mysql via CLI'); } + // I'm not sure about these? stream_set_blocking($pipes[1], false); // use non-blocking reads for stdout stream_set_blocking($pipes[2], false); // use non-blocking reads for stderr @@ -238,9 +395,9 @@ class RestoreFromBackup extends Command //$sql_contents = fopen($sqlfiles[0], "r"); //NOPE! This isn't a real file yet, silly-billy! - $sql_stat = $za->statIndex($sqlfile_indices[0]); - //$this->info("SQL Stat is: ".print_r($sql_stat,true)); - $sql_contents = $za->getStream($sql_stat['name']); + // FIXME - this feels like it wants to go somewhere else? + // and it doesn't seem 'right' - if you can't get a stream to the .sql file, + // why do we care what's happening with pipes and stdout and stderr?! if ($sql_contents === false) { $stdout = fgets($pipes[1]); $this->info($stdout); @@ -249,20 +406,27 @@ class RestoreFromBackup extends Command return false; } - $bytes_read = 0; try { - while (($buffer = fgets($sql_contents, self::$buffer_size)) !== false) { - $bytes_read += strlen($buffer); - // \Log::debug("Buffer is: '$buffer'"); + if ( $this->option('sanitize-with-prefix') === null) { + // "Legacy" direct-piping + $bytes_read = 0; + while (($buffer = fgets($sql_contents, SQLStreamer::$buffer_size)) !== false) { + $bytes_read += strlen($buffer); + // \Log::debug("Buffer is: '$buffer'"); $bytes_written = fwrite($pipes[0], $buffer); - if ($bytes_written === false) { - throw new Exception("Unable to write to pipe"); + if ($bytes_written === false) { + throw new Exception("Unable to write to pipe"); + } } + } else { + $sql_importer = new SQLStreamer($sql_contents, $pipes[0], $this->option('sanitize-with-prefix')); + $bytes_read = $sql_importer->line_aware_piping(); } } catch (\Exception $e) { \Log::error("Error during restore!!!! ".$e->getMessage()); + // FIXME - put these back and/or put them in the right places?! $err_out = fgets($pipes[1]); $err_err = fgets($pipes[2]); \Log::error("Error OUTPUT: ".$err_out); @@ -271,7 +435,6 @@ class RestoreFromBackup extends Command $this->error($err_err); throw $e; } - if (!feof($sql_contents) || $bytes_read == 0) { return $this->error("Not at end of file for sql file, or zero bytes read. aborting!"); } @@ -303,7 +466,7 @@ class RestoreFromBackup extends Command $fp = $za->getStream($ugly_file_name); //$this->info("Weird problem, here are file details? ".print_r($file_details,true)); $migrated_file = fopen($file_details['dest'].'/'.basename($pretty_file_name), 'w'); - while (($buffer = fgets($fp, self::$buffer_size)) !== false) { + while (($buffer = fgets($fp, SQLStreamer::$buffer_size)) !== false) { fwrite($migrated_file, $buffer); } fclose($migrated_file); diff --git a/app/Http/Controllers/Api/AssetMaintenancesController.php b/app/Http/Controllers/Api/AssetMaintenancesController.php index 931e8e51c0..4b47c323c2 100644 --- a/app/Http/Controllers/Api/AssetMaintenancesController.php +++ b/app/Http/Controllers/Api/AssetMaintenancesController.php @@ -36,7 +36,8 @@ class AssetMaintenancesController extends Controller { $this->authorize('view', Asset::class); - $maintenances = AssetMaintenance::select('asset_maintenances.*')->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'admin'); + $maintenances = AssetMaintenance::select('asset_maintenances.*') + ->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'admin'); if ($request->filled('search')) { $maintenances = $maintenances->TextSearch($request->input('search')); @@ -47,7 +48,7 @@ class AssetMaintenancesController extends Controller } if ($request->filled('supplier_id')) { - $maintenances->where('supplier_id', '=', $request->input('supplier_id')); + $maintenances->where('asset_maintenances.supplier_id', '=', $request->input('supplier_id')); } if ($request->filled('asset_maintenance_type')) { @@ -70,10 +71,13 @@ class AssetMaintenancesController extends Controller 'notes', 'asset_tag', 'asset_name', + 'serial', 'user_id', 'supplier', 'is_warranty', + 'status_label', ]; + $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at'; @@ -90,6 +94,12 @@ class AssetMaintenancesController extends Controller case 'asset_name': $maintenances = $maintenances->OrderByAssetName($order); break; + case 'serial': + $maintenances = $maintenances->OrderByAssetSerial($order); + break; + case 'status_label': + $maintenances = $maintenances->OrderStatusName($order); + break; default: $maintenances = $maintenances->orderBy($sort, $order); break; diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index ecbbe7a78c..cabf063f3d 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -903,6 +903,13 @@ class AssetsController extends Controller $originalValues['action_date'] = $checkin_at; } + if(!empty($asset->licenseseats->all())){ + foreach ($asset->licenseseats as $seat){ + $seat->assigned_to = null; + $seat->save(); + } + } + if ($asset->save()) { event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues)); diff --git a/app/Http/Controllers/Api/LocationsController.php b/app/Http/Controllers/Api/LocationsController.php index b888493286..e1c79dfbe4 100644 --- a/app/Http/Controllers/Api/LocationsController.php +++ b/app/Http/Controllers/Api/LocationsController.php @@ -25,9 +25,27 @@ class LocationsController extends Controller { $this->authorize('view', Location::class); $allowed_columns = [ - 'id', 'name', 'address', 'address2', 'city', 'state', 'country', 'zip', 'created_at', - 'updated_at', 'manager_id', 'image', - 'assigned_assets_count', 'users_count', 'assets_count','assigned_assets_count', 'assets_count', 'rtd_assets_count', 'currency', 'ldap_ou', ]; + 'id', + 'name', + 'address', + 'address2', + 'city', + 'state', + 'country', + 'zip', + 'created_at', + 'updated_at', + 'manager_id', + 'image', + 'assigned_assets_count', + 'users_count', + 'assets_count', + 'assigned_assets_count', + 'assets_count', + 'rtd_assets_count', + 'currency', + 'ldap_ou', + ]; $locations = Location::with('parent', 'manager', 'children')->select([ 'locations.id', @@ -50,6 +68,7 @@ class LocationsController extends Controller ])->withCount('assignedAssets as assigned_assets_count') ->withCount('assets as assets_count') ->withCount('rtd_assets as rtd_assets_count') + ->withCount('children as children_count') ->withCount('users as users_count'); if ($request->filled('search')) { @@ -80,6 +99,10 @@ class LocationsController extends Controller $locations->where('locations.country', '=', $request->input('country')); } + if ($request->filled('manager_id')) { + $locations->where('locations.manager_id', '=', $request->input('manager_id')); + } + // Make sure the offset and limit are actually integers and do not exceed system limits $offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value'); $limit = app('api_limit_value'); diff --git a/app/Http/Controllers/Api/ReportsController.php b/app/Http/Controllers/Api/ReportsController.php index 5c6eaebf50..a91d8a9bcc 100644 --- a/app/Http/Controllers/Api/ReportsController.php +++ b/app/Http/Controllers/Api/ReportsController.php @@ -33,7 +33,12 @@ class ReportsController extends Controller if (($request->filled('item_type')) && ($request->filled('item_id'))) { $actionlogs = $actionlogs->where('item_id', '=', $request->input('item_id')) - ->where('item_type', '=', 'App\\Models\\'.ucwords($request->input('item_type'))); + ->where('item_type', '=', 'App\\Models\\'.ucwords($request->input('item_type'))) + ->orWhere(function($query) use ($request) + { + $query->where('target_id', '=', $request->input('item_id')) + ->where('target_type', '=', 'App\\Models\\'.ucwords($request->input('item_type'))); + }); } if ($request->filled('action_type')) { diff --git a/app/Http/Controllers/Api/SettingsController.php b/app/Http/Controllers/Api/SettingsController.php index a0438ef078..a481d67944 100644 --- a/app/Http/Controllers/Api/SettingsController.php +++ b/app/Http/Controllers/Api/SettingsController.php @@ -148,7 +148,7 @@ class SettingsController extends Controller * * @author [A. Gianotto] [] * @since [v3.0] - * @return Redirect + * @return JsonResponse */ public function ajaxTestEmail() { @@ -170,7 +170,7 @@ class SettingsController extends Controller * * @author [A. Gianotto] [] * @since [v5.0.0] - * @return Response + * @return JsonResponse */ public function purgeBarcodes() { @@ -211,7 +211,7 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * @since [v5.0.0] * @param \Illuminate\Http\Request $request - * @return array + * @return array | JsonResponse */ public function showLoginAttempts(Request $request) { @@ -229,6 +229,12 @@ class SettingsController extends Controller } + /** + * Lists backup files + * + * @author [A. Gianotto] + * @return array | JsonResponse + */ public function listBackups() { $settings = Setting::getSettings(); $path = 'app/backups'; @@ -249,12 +255,12 @@ class SettingsController extends Controller 'filesize' => Setting::fileSizeConvert(Storage::size($backup_files[$f])), 'modified_value' => $file_timestamp, 'modified_display' => date($settings->date_display_format.' '.$settings->time_display_format, $file_timestamp), + 'backup_url' => config('app.url').'/settings/backups/download/'.basename($backup_files[$f]), ]; $count++; } - } } @@ -264,15 +270,56 @@ class SettingsController extends Controller } + /** + * Downloads a backup file. + * We use response()->download() here instead of Storage::download() because Storage::download() + * exhausts memory on larger files. + * + * @author [A. Gianotto] + * @return JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse + */ public function downloadBackup($file) { - $path = 'app/backups'; - if (Storage::exists($path.'/'.$file)) { + $path = storage_path('app/backups'); + + if (Storage::exists('app/backups/'.$file)) { $headers = ['ContentType' => 'application/zip']; - return Storage::download($path.'/'.$file, $file, $headers); + return response()->download($path.'/'.$file, $file, $headers); } else { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_not_found'))); + return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_not_found')), 404); } } + + /** + * Determines and downloads the latest backup + * + * @author [A. Gianotto] + * @since [v6.3.1] + * @return JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse + */ + public function downloadLatestBackup() { + + $fileData = collect(); + foreach (Storage::files('app/backups') as $file) { + if (pathinfo($file, PATHINFO_EXTENSION) == 'zip') { + $fileData->push([ + 'file' => $file, + 'date' => Storage::lastModified($file) + ]); + } + } + + $newest = $fileData->sortByDesc('date')->first(); + if (Storage::exists($newest['file'])) { + $headers = ['ContentType' => 'application/zip']; + return response()->download(storage_path($newest['file']), basename($newest['file']), $headers); + } else { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_not_found')), 404); + } + + + } + + } \ No newline at end of file diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index 3eb7783e3d..6fbaf281b0 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -274,11 +274,6 @@ class UsersController extends Controller $offset = ($request->input('offset') > $users->count()) ? $users->count() : app('api_offset_value'); $limit = app('api_limit_value'); - \Log::debug('Requested offset: '. $request->input('offset')); - \Log::debug('App offset: '. app('api_offset_value')); - \Log::debug('Actual offset: '. $offset); - \Log::debug('Limit: '. $limit); - $total = $users->count(); $users = $users->skip($offset)->take($limit)->get(); diff --git a/app/Http/Controllers/AssetMaintenancesController.php b/app/Http/Controllers/AssetMaintenancesController.php index dc6bc84347..b5a50925ea 100644 --- a/app/Http/Controllers/AssetMaintenancesController.php +++ b/app/Http/Controllers/AssetMaintenancesController.php @@ -148,30 +148,20 @@ class AssetMaintenancesController extends Controller */ public function edit($assetMaintenanceId = null) { + $this->authorize('update', Asset::class); + // Check if the asset maintenance exists $this->authorize('update', Asset::class); // Check if the asset maintenance exists if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) { - // Redirect to the improvement management page - return redirect()->route('maintenances.index') - ->with('error', trans('admin/asset_maintenances/message.not_found')); - } elseif (! $assetMaintenance->asset) { - return redirect()->route('maintenances.index') - ->with('error', 'The asset associated with this maintenance does not exist.'); + // Redirect to the asset maintenance management page + return redirect()->route('maintenances.index')->with('error', trans('admin/asset_maintenances/message.not_found')); + } elseif ((!$assetMaintenance->asset) || ($assetMaintenance->asset->deleted_at!='')) { + // Redirect to the asset maintenance management page + return redirect()->route('maintenances.index')->with('error', 'asset does not exist'); } elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) { return static::getInsufficientPermissionsRedirect(); } - if ($assetMaintenance->completion_date == '0000-00-00') { - $assetMaintenance->completion_date = null; - } - - if ($assetMaintenance->start_date == '0000-00-00') { - $assetMaintenance->start_date = null; - } - - if ($assetMaintenance->cost == '0.00') { - $assetMaintenance->cost = null; - } // Prepare Improvement Type List $assetMaintenanceType = [ @@ -203,8 +193,10 @@ class AssetMaintenancesController extends Controller // Check if the asset maintenance exists if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) { // Redirect to the asset maintenance management page - return redirect()->route('maintenances.index') - ->with('error', trans('admin/asset_maintenances/message.not_found')); + return redirect()->route('maintenances.index')->with('error', trans('admin/asset_maintenances/message.not_found')); + } elseif ((!$assetMaintenance->asset) || ($assetMaintenance->asset->deleted_at!='')) { + // Redirect to the asset maintenance management page + return redirect()->route('maintenances.index')->with('error', 'asset does not exist'); } elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) { return static::getInsufficientPermissionsRedirect(); } diff --git a/app/Http/Controllers/AssetModelsController.php b/app/Http/Controllers/AssetModelsController.php index 012f40e399..484a2e2f85 100755 --- a/app/Http/Controllers/AssetModelsController.php +++ b/app/Http/Controllers/AssetModelsController.php @@ -442,7 +442,6 @@ class AssetModelsController extends Controller $del_count = 0; foreach ($models as $model) { - \Log::debug($model->id); if ($model->assets_count > 0) { $del_error_count++; @@ -452,8 +451,6 @@ class AssetModelsController extends Controller } } - \Log::debug($del_count); - \Log::debug($del_error_count); if ($del_error_count == 0) { return redirect()->route('models.index') diff --git a/app/Http/Controllers/LocationsController.php b/app/Http/Controllers/LocationsController.php index 08dc38b3ac..7a3691684f 100755 --- a/app/Http/Controllers/LocationsController.php +++ b/app/Http/Controllers/LocationsController.php @@ -8,6 +8,7 @@ use App\Models\Location; use App\Models\User; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Storage; +use Illuminate\Http\Request; /** * This controller handles all actions related to Locations for @@ -168,7 +169,7 @@ class LocationsController extends Controller { $this->authorize('delete', Location::class); if (is_null($location = Location::find($locationId))) { - return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.not_found')); + return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.does_not_exist')); } if ($location->users()->count() > 0) { @@ -238,7 +239,7 @@ class LocationsController extends Controller * @author [A. Gianotto] [] * @param int $locationId * @since [v6.0.14] - * @return View + * @return \Illuminate\Contracts\View\View */ public function getClone($locationId = null) { @@ -272,8 +273,97 @@ class LocationsController extends Controller } return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist')); + } + /** + * Returns a view that allows the user to bulk delete locations + * + * @author [A. Gianotto] [] + * @since [v6.3.1] + * @return \Illuminate\Contracts\View\View + */ + public function postBulkDelete(Request $request) + { + $locations_raw_array = $request->input('ids'); + + // Make sure some IDs have been selected + if ((is_array($locations_raw_array)) && (count($locations_raw_array) > 0)) { + $locations = Location::whereIn('id', $locations_raw_array) + ->withCount('assignedAssets as assigned_assets_count') + ->withCount('assets as assets_count') + ->withCount('rtd_assets as rtd_assets_count') + ->withCount('children as children_count') + ->withCount('users as users_count')->get(); + + $valid_count = 0; + foreach ($locations as $location) { + if ($location->isDeletable()) { + $valid_count++; + } + } + return view('locations/bulk-delete', compact('locations'))->with('valid_count', $valid_count); + } + + return redirect()->route('models.index') + ->with('error', 'You must select at least one model to edit.'); + } + + /** + * Checks that locations can be deleted and deletes them if they can + * + * @author [A. Gianotto] [] + * @since [v6.3.1] + * @return \Illuminate\Http\RedirectResponse + */ + public function postBulkDeleteStore(Request $request) { + $locations_raw_array = $request->input('ids'); + + if ((is_array($locations_raw_array)) && (count($locations_raw_array) > 0)) { + $locations = Location::whereIn('id', $locations_raw_array)->get(); + + $success_count = 0; + $error_count = 0; + + foreach ($locations as $location) { + + // Can we delete this location? + if ($location->isDeletable()) { + $location->delete(); + $success_count++; + } else { + $error_count++; + } + } + + \Log::debug('Success count: '.$success_count); + \Log::debug('Error count: '.$error_count); + // Complete success + if ($success_count == count($locations_raw_array)) { + return redirect() + ->route('locations.index') + ->with('success', trans_choice('general.bulk.delete.success', $success_count, + ['object_type' => trans_choice('general.location_plural', $success_count), 'count' => $success_count] + )); + } + + // Partial success + if ($error_count > 0) { + return redirect() + ->route('locations.index') + ->with('warning', trans('general.bulk.partial_success', + ['success' => $success_count, 'error' => $error_count, 'object_type' => trans('general.locations')] + )); + } + } + + + // Nothing was selected - return to the index + return redirect() + ->route('locations.index') + ->with('error', trans('general.bulk.nothing_selected', + ['object_type' => trans('general.locations')] + )); } } diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 162469bcd2..d03261bc29 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -296,8 +296,8 @@ class ReportsController extends Controller e($actionlog->itemType()), ($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name, ($actionlog->item) ? $actionlog->item->serial : null, - ($actionlog->item->model) ? htmlspecialchars($actionlog->item->model->name, ENT_NOQUOTES) : null, - ($actionlog->item->model) ? $actionlog->item->model->model_number : null, + (($actionlog->item) && ($actionlog->item->model)) ? htmlspecialchars($actionlog->item->model->name, ENT_NOQUOTES) : null, + (($actionlog->item) && ($actionlog->item->model)) ? $actionlog->item->model->model_number : null, $target_name, ($actionlog->note) ? e($actionlog->note) : '', $actionlog->log_meta, diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index 31ea724c0e..a7b6bda1f8 100755 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -425,65 +425,64 @@ class SettingsController extends Controller if (! config('app.lock_passwords')) { $setting->site_name = $request->input('site_name'); $setting->custom_css = $request->input('custom_css'); - } + $setting = $request->handleImages($setting, 600, 'logo', '', 'logo'); - $setting = $request->handleImages($setting, 600, 'logo', '', 'logo'); - - if ('1' == $request->input('clear_logo')) { + if ('1' == $request->input('clear_logo')) { Storage::disk('public')->delete($setting->logo); - $setting->logo = null; + $setting->logo = null; $setting->brand = 1; - } - - - $setting = $request->handleImages($setting, 600, 'email_logo', '', 'email_logo'); - - - if ('1' == $request->input('clear_email_logo')) { - Storage::disk('public')->delete($setting->email_logo); - $setting->email_logo = null; - // If they are uploading an image, validate it and upload it - } - - - $setting = $request->handleImages($setting, 600, 'label_logo', '', 'label_logo'); - - - if ('1' == $request->input('clear_label_logo')) { - Storage::disk('public')->delete($setting->label_logo); - $setting->label_logo = null; - } - - - // If the user wants to clear the favicon... - if ($request->hasFile('favicon')) { - $favicon_image = $favicon_upload = $request->file('favicon'); - $favicon_ext = $favicon_image->getClientOriginalExtension(); - $setting->favicon = $favicon_file_name = 'favicon-uploaded.'.$favicon_ext; - - if (($favicon_image->getClientOriginalExtension() != 'ico') && ($favicon_image->getClientOriginalExtension() != 'svg')) { - $favicon_upload = Image::make($favicon_image->getRealPath())->resize(null, 36, function ($constraint) { - $constraint->aspectRatio(); - $constraint->upsize(); - }); - - // This requires a string instead of an object, so we use ($string) - Storage::disk('public')->put($favicon_file_name, (string) $favicon_upload->encode()); - } else { - Storage::disk('public')->put($favicon_file_name, file_get_contents($request->file('favicon'))); } - // Remove Current image if exists - if (($setting->favicon) && (file_exists($favicon_file_name))) { - Storage::disk('public')->delete($favicon_file_name); - } - } elseif ('1' == $request->input('clear_favicon')) { - Storage::disk('public')->delete($setting->clear_favicon); - $setting->favicon = null; + $setting = $request->handleImages($setting, 600, 'email_logo', '', 'email_logo'); - // If they are uploading an image, validate it and upload it - } + + if ('1' == $request->input('clear_email_logo')) { + Storage::disk('public')->delete($setting->email_logo); + $setting->email_logo = null; + // If they are uploading an image, validate it and upload it + } + + + + $setting = $request->handleImages($setting, 600, 'label_logo', '', 'label_logo'); + + if ('1' == $request->input('clear_label_logo')) { + Storage::disk('public')->delete($setting->label_logo); + $setting->label_logo = null; + } + + + // If the user wants to clear the favicon... + if ($request->hasFile('favicon')) { + $favicon_image = $favicon_upload = $request->file('favicon'); + $favicon_ext = $favicon_image->getClientOriginalExtension(); + $setting->favicon = $favicon_file_name = 'favicon-uploaded.'.$favicon_ext; + + if (($favicon_image->getClientOriginalExtension() != 'ico') && ($favicon_image->getClientOriginalExtension() != 'svg')) { + $favicon_upload = Image::make($favicon_image->getRealPath())->resize(null, 36, function ($constraint) { + $constraint->aspectRatio(); + $constraint->upsize(); + }); + + // This requires a string instead of an object, so we use ($string) + Storage::disk('public')->put($favicon_file_name, (string) $favicon_upload->encode()); + } else { + Storage::disk('public')->put($favicon_file_name, file_get_contents($request->file('favicon'))); + } + + + // Remove Current image if exists + if (($setting->favicon) && (file_exists($favicon_file_name))) { + Storage::disk('public')->delete($favicon_file_name); + } + } elseif ('1' == $request->input('clear_favicon')) { + Storage::disk('public')->delete($setting->clear_favicon); + $setting->favicon = null; + + // If they are uploading an image, validate it and upload it + } + } if ($setting->save()) { return redirect()->route('settings.index') diff --git a/app/Http/Requests/ImageUploadRequest.php b/app/Http/Requests/ImageUploadRequest.php index 6408b2b34b..d9947efe7b 100644 --- a/app/Http/Requests/ImageUploadRequest.php +++ b/app/Http/Requests/ImageUploadRequest.php @@ -97,7 +97,7 @@ class ImageUploadRequest extends Request if (!config('app.lock_passwords')) { - $ext = $image->getClientOriginalExtension(); + $ext = $image->guessExtension(); $file_name = $type.'-'.$form_fieldname.'-'.$item->id.'-'.str_random(10).'.'.$ext; \Log::info('File name will be: '.$file_name); diff --git a/app/Http/Transformers/AssetMaintenancesTransformer.php b/app/Http/Transformers/AssetMaintenancesTransformer.php index 01ef2adb87..88ac447c25 100644 --- a/app/Http/Transformers/AssetMaintenancesTransformer.php +++ b/app/Http/Transformers/AssetMaintenancesTransformer.php @@ -28,12 +28,20 @@ class AssetMaintenancesTransformer 'id' => (int) $assetmaintenance->asset->id, 'name'=> ($assetmaintenance->asset->name) ? e($assetmaintenance->asset->name) : null, 'asset_tag'=> e($assetmaintenance->asset->asset_tag), - + 'serial'=> e($assetmaintenance->asset->serial), + 'deleted_at'=> e($assetmaintenance->asset->deleted_at), + 'created_at'=> e($assetmaintenance->asset->created_at), ] : null, 'model' => (($assetmaintenance->asset) && ($assetmaintenance->asset->model)) ? [ 'id' => (int) $assetmaintenance->asset->model->id, 'name'=> ($assetmaintenance->asset->model->name) ? e($assetmaintenance->asset->model->name).' '.e($assetmaintenance->asset->model->model_number) : null, ] : null, + 'status_label' => ($assetmaintenance->asset->assetstatus) ? [ + 'id' => (int) $assetmaintenance->asset->assetstatus->id, + 'name'=> e($assetmaintenance->asset->assetstatus->name), + 'status_type'=> e($assetmaintenance->asset->assetstatus->getStatuslabelType()), + 'status_meta' => e($assetmaintenance->asset->present()->statusMeta), + ] : null, 'company' => (($assetmaintenance->asset) && ($assetmaintenance->asset->company)) ? [ 'id' => (int) $assetmaintenance->asset->company->id, 'name'=> ($assetmaintenance->asset->company->name) ? e($assetmaintenance->asset->company->name) : null, @@ -64,7 +72,7 @@ class AssetMaintenancesTransformer ]; $permissions_array['available_actions'] = [ - 'update' => Gate::allows('update', Asset::class), + 'update' => (Gate::allows('update', Asset::class) && ($assetmaintenance->asset->deleted_at=='')) ? true : false, 'delete' => Gate::allows('delete', Asset::class), ]; diff --git a/app/Http/Transformers/LocationsTransformer.php b/app/Http/Transformers/LocationsTransformer.php index 635a90cbc7..513b967f42 100644 --- a/app/Http/Transformers/LocationsTransformer.php +++ b/app/Http/Transformers/LocationsTransformer.php @@ -65,6 +65,9 @@ class LocationsTransformer $permissions_array['available_actions'] = [ 'update' => Gate::allows('update', Location::class) ? true : false, 'delete' => $location->isDeletable(), + 'bulk_selectable' => [ + 'delete' => $location->isDeletable() + ], 'clone' => (Gate::allows('create', Location::class) && ($location->deleted_at == '')), ]; diff --git a/app/Importer/AccessoryImporter.php b/app/Importer/AccessoryImporter.php index 9901fb70d7..eb17c5acad 100644 --- a/app/Importer/AccessoryImporter.php +++ b/app/Importer/AccessoryImporter.php @@ -46,10 +46,9 @@ class AccessoryImporter extends ItemImporter $this->item['min_amt'] = $this->findCsvMatch($row, "min_amt"); $accessory->fill($this->sanitizeItemForStoring($accessory)); - //FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything. - // $accessory->unsetEventDispatcher(); + // This sets an attribute on the Loggable trait for the action log + $accessory->setImported(true); if ($accessory->save()) { - $accessory->logCreate('Imported using CSV Importer'); $this->log('Accessory '.$this->item['name'].' was created'); return; diff --git a/app/Importer/AssetImporter.php b/app/Importer/AssetImporter.php index cf762a8fd4..e001a383ad 100644 --- a/app/Importer/AssetImporter.php +++ b/app/Importer/AssetImporter.php @@ -135,10 +135,10 @@ class AssetImporter extends ItemImporter $asset->{$custom_field} = $val; } } - + // This sets an attribute on the Loggable trait for the action log + $asset->setImported(true); if ($asset->save()) { - $asset->logCreate(trans('general.importer.import_note')); $this->log('Asset '.$this->item['name'].' with serial number '.$this->item['serial'].' was created'); // If we have a target to checkout to, lets do so. diff --git a/app/Importer/ComponentImporter.php b/app/Importer/ComponentImporter.php index 71ded1b0e5..f72d4cbfd7 100644 --- a/app/Importer/ComponentImporter.php +++ b/app/Importer/ComponentImporter.php @@ -48,10 +48,10 @@ class ComponentImporter extends ItemImporter $this->log('No matching component, creating one'); $component = new Component; $component->fill($this->sanitizeItemForStoring($component)); - //FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything. - $component->unsetEventDispatcher(); + + // This sets an attribute on the Loggable trait for the action log + $component->setImported(true); if ($component->save()) { - $component->logCreate('Imported using CSV Importer'); $this->log('Component '.$this->item['name'].' was created'); // If we have an asset tag, checkout to that asset. diff --git a/app/Importer/ConsumableImporter.php b/app/Importer/ConsumableImporter.php index 5a65514deb..9e7019b086 100644 --- a/app/Importer/ConsumableImporter.php +++ b/app/Importer/ConsumableImporter.php @@ -45,10 +45,10 @@ class ConsumableImporter extends ItemImporter $this->item['item_no'] = trim($this->findCsvMatch($row, 'item_number')); $this->item['min_amt'] = trim($this->findCsvMatch($row, "min_amt")); $consumable->fill($this->sanitizeItemForStoring($consumable)); - //FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything. - $consumable->unsetEventDispatcher(); + + // This sets an attribute on the Loggable trait for the action log + $consumable->setImported(true); if ($consumable->save()) { - $consumable->logCreate('Imported using CSV Importer'); $this->log('Consumable '.$this->item['name'].' was created'); return; diff --git a/app/Importer/LicenseImporter.php b/app/Importer/LicenseImporter.php index 393d00367d..b7c55cdba6 100644 --- a/app/Importer/LicenseImporter.php +++ b/app/Importer/LicenseImporter.php @@ -85,10 +85,10 @@ class LicenseImporter extends ItemImporter } else { $license->fill($this->sanitizeItemForStoring($license)); } - //FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything. - // $license->unsetEventDispatcher(); + + // This sets an attribute on the Loggable trait for the action log + $license->setImported(true); if ($license->save()) { - $license->logCreate('Imported using csv importer'); $this->log('License '.$this->item['name'].' with serial number '.$this->item['serial'].' was created'); // Lets try to checkout seats if the fields exist and we have seats. diff --git a/app/Listeners/CheckoutableListener.php b/app/Listeners/CheckoutableListener.php index 6aa0a0d376..9768f2b956 100644 --- a/app/Listeners/CheckoutableListener.php +++ b/app/Listeners/CheckoutableListener.php @@ -69,9 +69,9 @@ class CheckoutableListener } } } catch (ClientException $e) { - Log::debug("Exception caught during checkout notification: " . $e->getMessage()); + Log::warning("Exception caught during checkout notification: " . $e->getMessage()); } catch (Exception $e) { - Log::error("Exception caught during checkout notification: " . $e->getMessage()); + Log::warning("Exception caught during checkout notification: " . $e->getMessage()); } } @@ -115,7 +115,7 @@ class CheckoutableListener ); } //slack doesn't include the url in its messaging format so this is needed to hit the endpoint - if(Setting::getSettings()->webhook_selected =='slack') { + if(Setting::getSettings()->webhook_selected =='slack' || Setting::getSettings()->webhook_selected =='general') { if ($this->shouldSendWebhookNotification()) { Notification::route('slack', Setting::getSettings()->webhook_endpoint) @@ -124,9 +124,9 @@ class CheckoutableListener } } catch (ClientException $e) { - Log::debug("Exception caught during checkout notification: " . $e->getMessage()); + Log::warning("Exception caught during checkout notification: " . $e->getMessage()); } catch (Exception $e) { - Log::error("Exception caught during checkin notification: " . $e->getMessage()); + Log::warning("Exception caught during checkin notification: " . $e->getMessage()); } } diff --git a/app/Models/Actionlog.php b/app/Models/Actionlog.php index bc08aa800a..90e0e884f8 100755 --- a/app/Models/Actionlog.php +++ b/app/Models/Actionlog.php @@ -19,6 +19,9 @@ class Actionlog extends SnipeModel { use HasFactory; + // This is to manually set the source (via setActionSource()) for determineActionSource() + protected ?string $source = null; + protected $presenter = \App\Presenters\ActionlogPresenter::class; use SoftDeletes; use Presentable; @@ -341,7 +344,12 @@ class Actionlog extends SnipeModel * @since v6.3.0 * @return string */ - public function determineActionSource() { + public function determineActionSource(): string + { + // This is a manually set source + if($this->source) { + return $this->source; + } // This is an API call if (((request()->header('content-type') && (request()->header('accept'))=='application/json')) @@ -358,4 +366,10 @@ class Actionlog extends SnipeModel return 'cli/unknown'; } + + // Manually sets $this->source for determineActionSource() + public function setActionSource($source = null): void + { + $this->source = $source; + } } diff --git a/app/Models/Asset.php b/app/Models/Asset.php index abb2239dc7..c2a2a8d995 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -1560,7 +1560,7 @@ class Asset extends Depreciable * * In short, this set of statements tells the query builder to ONLY query against an * actual field that's being passed if it doesn't meet known relational fields. This - * allows us to query custom fields directly in the assetsv table + * allows us to query custom fields directly in the assets table * (regardless of their name) and *skip* any fields that we already know can only be * searched through relational searches that we do earlier in this method. * diff --git a/app/Models/AssetMaintenance.php b/app/Models/AssetMaintenance.php index 760abf1519..5f66783cbb 100644 --- a/app/Models/AssetMaintenance.php +++ b/app/Models/AssetMaintenance.php @@ -62,7 +62,15 @@ class AssetMaintenance extends Model implements ICompanyableChild * * @var array */ - protected $searchableAttributes = ['title', 'notes', 'asset_maintenance_type', 'cost', 'start_date', 'completion_date']; + protected $searchableAttributes = + [ + 'title', + 'notes', + 'asset_maintenance_type', + 'cost', + 'start_date', + 'completion_date' + ]; /** * The relations and their attributes that should be included when searching the model. @@ -70,9 +78,10 @@ class AssetMaintenance extends Model implements ICompanyableChild * @var array */ protected $searchableRelations = [ - 'asset' => ['name', 'asset_tag'], + 'asset' => ['name', 'asset_tag', 'serial'], 'asset.model' => ['name', 'model_number'], 'asset.supplier' => ['name'], + 'asset.assetstatus' => ['name'], 'supplier' => ['name'], ]; @@ -197,6 +206,7 @@ class AssetMaintenance extends Model implements ICompanyableChild ->orderBy('suppliers_maintenances.name', $order); } + /** * Query builder scope to order on admin user * @@ -239,4 +249,33 @@ class AssetMaintenance extends Model implements ICompanyableChild return $query->leftJoin('assets', 'asset_maintenances.asset_id', '=', 'assets.id') ->orderBy('assets.name', $order); } + + /** + * Query builder scope to order on serial + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ + public function scopeOrderByAssetSerial($query, $order) + { + return $query->leftJoin('assets', 'asset_maintenances.asset_id', '=', 'assets.id') + ->orderBy('assets.serial', $order); + } + + /** + * Query builder scope to order on status label name + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ + public function scopeOrderStatusName($query, $order) + { + return $query->join('assets as maintained_asset', 'asset_maintenances.asset_id', '=', 'maintained_asset.id') + ->leftjoin('status_labels as maintained_asset_status', 'maintained_asset_status.id', '=', 'maintained_asset.status_id') + ->orderBy('maintained_asset_status.name', $order); + } } diff --git a/app/Models/Location.php b/app/Models/Location.php index 145d6cef9a..2965ff2fc0 100755 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -95,7 +95,10 @@ class Location extends SnipeModel /** - * Determine whether or not this location can be deleted + * Determine whether or not this location can be deleted. + * + * This method requires the eager loading of the relationships in order to determine whether + * it can be deleted. It's tempting to load those here, but that increases the query load considerably. * * @author A. Gianotto * @since [v3.0] @@ -104,9 +107,10 @@ class Location extends SnipeModel public function isDeletable() { return Gate::allows('delete', $this) - && ($this->assignedAssets()->count() === 0) - && ($this->assets()->count() === 0) - && ($this->users()->count() === 0); + && ($this->assets_count === 0) + && ($this->assigned_assets_count === 0) + && ($this->children_count === 0) + && ($this->users_count === 0); } /** diff --git a/app/Models/Loggable.php b/app/Models/Loggable.php index ce3a07f159..9e9355ea74 100644 --- a/app/Models/Loggable.php +++ b/app/Models/Loggable.php @@ -8,6 +8,9 @@ use Illuminate\Support\Facades\Auth; trait Loggable { + // an attribute for setting whether or not the item was imported + public ?bool $imported = false; + /** * @author Daniel Meltzer * @since [v3.4] @@ -18,6 +21,11 @@ trait Loggable return $this->morphMany(Actionlog::class, 'item'); } + public function setImported(bool $bool): void + { + $this->imported = $bool; + } + /** * @author Daniel Meltzer * @since [v3.4] diff --git a/app/Notifications/CheckinAccessoryNotification.php b/app/Notifications/CheckinAccessoryNotification.php index 04a1d07168..8cfca23e59 100644 --- a/app/Notifications/CheckinAccessoryNotification.php +++ b/app/Notifications/CheckinAccessoryNotification.php @@ -63,34 +63,8 @@ class CheckinAccessoryNotification extends Notification if ($this->target instanceof User && $this->target->email != '') { \Log::debug('The target is a user'); - /** - * Send an email if the asset requires acceptance, - * so the user can accept or decline the asset - */ - if (($this->item->requireAcceptance()) || ($this->item->getEula()) || ($this->item->checkin_email())) { - $notifyBy[] = 'mail'; - } - - /** - * Send an email if the asset requires acceptance, - * so the user can accept or decline the asset - */ - if ($this->item->requireAcceptance()) { - \Log::debug('This accessory requires acceptance'); - } - - /** - * Send an email if the item has a EULA, since the user should always receive it - */ - if ($this->item->getEula()) { - \Log::debug('This accessory has a EULA'); - } - - /** - * Send an email if an email should be sent at checkin/checkout - */ if ($this->item->checkin_email()) { - \Log::debug('This accessory has a checkin_email()'); + $notifyBy[] = 'mail'; } } diff --git a/app/Observers/AccessoryObserver.php b/app/Observers/AccessoryObserver.php index f97d166b17..ddf29681be 100644 --- a/app/Observers/AccessoryObserver.php +++ b/app/Observers/AccessoryObserver.php @@ -38,6 +38,9 @@ class AccessoryObserver $logAction->item_id = $accessory->id; $logAction->created_at = date('Y-m-d H:i:s'); $logAction->user_id = Auth::id(); + if($accessory->imported) { + $logAction->setActionSource('importer'); + } $logAction->logaction('create'); } diff --git a/app/Observers/AssetObserver.php b/app/Observers/AssetObserver.php index 2b0955fde6..8fcf2485a3 100644 --- a/app/Observers/AssetObserver.php +++ b/app/Observers/AssetObserver.php @@ -109,6 +109,9 @@ class AssetObserver $logAction->item_id = $asset->id; $logAction->created_at = date('Y-m-d H:i:s'); $logAction->user_id = Auth::id(); + if($asset->imported) { + $logAction->setActionSource('importer'); + } $logAction->logaction('create'); } diff --git a/app/Observers/ComponentObserver.php b/app/Observers/ComponentObserver.php index 57792022bb..a8bb386e8e 100644 --- a/app/Observers/ComponentObserver.php +++ b/app/Observers/ComponentObserver.php @@ -38,6 +38,9 @@ class ComponentObserver $logAction->item_id = $component->id; $logAction->created_at = date('Y-m-d H:i:s'); $logAction->user_id = Auth::id(); + if($component->imported) { + $logAction->setActionSource('importer'); + } $logAction->logaction('create'); } diff --git a/app/Observers/ConsumableObserver.php b/app/Observers/ConsumableObserver.php index b945196e20..1f0c777dcc 100644 --- a/app/Observers/ConsumableObserver.php +++ b/app/Observers/ConsumableObserver.php @@ -38,6 +38,9 @@ class ConsumableObserver $logAction->item_id = $consumable->id; $logAction->created_at = date('Y-m-d H:i:s'); $logAction->user_id = Auth::id(); + if($consumable->imported) { + $logAction->setActionSource('importer'); + } $logAction->logaction('create'); } diff --git a/app/Observers/LicenseObserver.php b/app/Observers/LicenseObserver.php index 1478aba113..0628020965 100644 --- a/app/Observers/LicenseObserver.php +++ b/app/Observers/LicenseObserver.php @@ -38,6 +38,9 @@ class LicenseObserver $logAction->item_id = $license->id; $logAction->created_at = date('Y-m-d H:i:s'); $logAction->user_id = Auth::id(); + if($license->imported) { + $logAction->setActionSource('importer'); + } $logAction->logaction('create'); } diff --git a/app/Presenters/AssetMaintenancesPresenter.php b/app/Presenters/AssetMaintenancesPresenter.php index c4446c0b2a..5f9694b44c 100644 --- a/app/Presenters/AssetMaintenancesPresenter.php +++ b/app/Presenters/AssetMaintenancesPresenter.php @@ -41,6 +41,19 @@ class AssetMaintenancesPresenter extends Presenter 'sortable' => true, 'title' => trans('admin/hardware/table.asset_tag'), 'formatter' => 'assetTagLinkFormatter', + ], [ + 'field' => 'serial', + 'searchable' => true, + 'sortable' => true, + 'title' => trans('admin/hardware/table.serial'), + 'formatter' => 'assetSerialLinkFormatter', + ], [ + 'field' => 'status_label', + 'searchable' => true, + 'sortable' => true, + 'title' => trans('admin/hardware/table.status'), + 'visible' => true, + 'formatter' => 'statuslabelsLinkObjFormatter', ], [ 'field' => 'model', 'searchable' => true, diff --git a/app/Presenters/LocationPresenter.php b/app/Presenters/LocationPresenter.php index 86e82c1220..6a9bc0b568 100644 --- a/app/Presenters/LocationPresenter.php +++ b/app/Presenters/LocationPresenter.php @@ -14,7 +14,11 @@ class LocationPresenter extends Presenter public static function dataTableLayout() { $layout = [ - + [ + 'field' => 'bulk_selectable', + 'checkbox' => true, + 'formatter' => 'checkboxEnabledFormatter', + ], [ 'field' => 'id', 'searchable' => false, diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php index 41dd80b4fc..1c89dc2b8c 100644 --- a/app/Providers/SettingsServiceProvider.php +++ b/app/Providers/SettingsServiceProvider.php @@ -39,24 +39,12 @@ class SettingsServiceProvider extends ServiceProvider $limit = abs($int_limit); } -// \Log::debug('Max in env: '.config('app.max_results')); -// \Log::debug('Original requested limit: '.request('limit')); -// \Log::debug('Int limit: '.$int_limit); -// \Log::debug('Modified limit: '.$limit); -// \Log::debug('------------------------------'); - - return $limit; }); // Make sure the offset is actually set and is an integer \App::singleton('api_offset_value', function () { $offset = intval(request('offset')); -// \Log::debug('Original requested offset: '.request('offset')); -// \Log::debug('Modified offset: '.$offset); -// \Log::debug('------------------------------'); - - return $offset; }); diff --git a/app/View/Label.php b/app/View/Label.php index 83184e4b04..fd6b172550 100644 --- a/app/View/Label.php +++ b/app/View/Label.php @@ -90,13 +90,9 @@ class Label implements View $assetData->put('id', $asset->id); $assetData->put('tag', $asset->asset_tag); - if ($template->getSupportTitle()) { - - if ($asset->company && !empty($settings->label2_title)) { - $title = str_replace('{COMPANY}', $asset->company->name, $settings->label2_title); - $settings->qr_text; - $assetData->put('title', $title); - } + if ($template->getSupportTitle() && !empty($settings->label2_title)) { + $title = str_replace('{COMPANY}', data_get($asset, 'company.name'), $settings->label2_title); + $assetData->put('title', $title); } if ($template->getSupportLogo()) { @@ -216,4 +212,4 @@ class Label implements View return self::NAME; } -} \ No newline at end of file +} diff --git a/composer.json b/composer.json index 207d7d9f8c..0ff2cd999c 100644 --- a/composer.json +++ b/composer.json @@ -77,7 +77,8 @@ "watson/validating": "^6.1" }, "suggest": { - "ext-ldap": "*" + "ext-ldap": "*", + "ext-zip": "*" }, "require-dev": { "brianium/paratest": "^6.6", diff --git a/composer.lock b/composer.lock index 961467e19c..d1c073cb5a 100644 --- a/composer.lock +++ b/composer.lock @@ -4804,16 +4804,16 @@ }, { "name": "league/oauth2-server", - "version": "8.3.5", + "version": "8.4.2", "source": { "type": "git", "url": "https://github.com/thephpleague/oauth2-server.git", - "reference": "7aeb7c42b463b1a6fe4d084d3145e2fa22436876" + "reference": "007dc5f6c0151a73b133fec36c9686cc956209d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/7aeb7c42b463b1a6fe4d084d3145e2fa22436876", - "reference": "7aeb7c42b463b1a6fe4d084d3145e2fa22436876", + "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/007dc5f6c0151a73b133fec36c9686cc956209d3", + "reference": "007dc5f6c0151a73b133fec36c9686cc956209d3", "shasum": "" }, "require": { @@ -4880,7 +4880,7 @@ ], "support": { "issues": "https://github.com/thephpleague/oauth2-server/issues", - "source": "https://github.com/thephpleague/oauth2-server/tree/8.3.5" + "source": "https://github.com/thephpleague/oauth2-server/tree/8.4.2" }, "funding": [ { @@ -4888,7 +4888,7 @@ "type": "github" } ], - "time": "2022-05-03T21:21:28+00:00" + "time": "2023-08-02T22:54:39+00:00" }, { "name": "league/uri", @@ -9669,16 +9669,16 @@ }, { "name": "symfony/http-kernel", - "version": "v5.4.10", + "version": "v5.4.20", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "255ae3b0a488d78fbb34da23d3e0c059874b5948" + "reference": "aaeec341582d3c160cc9ecfa8b2419ba6c69954e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/255ae3b0a488d78fbb34da23d3e0c059874b5948", - "reference": "255ae3b0a488d78fbb34da23d3e0c059874b5948", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/aaeec341582d3c160cc9ecfa8b2419ba6c69954e", + "reference": "aaeec341582d3c160cc9ecfa8b2419ba6c69954e", "shasum": "" }, "require": { @@ -9761,7 +9761,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.4.10" + "source": "https://github.com/symfony/http-kernel/tree/v5.4.20" }, "funding": [ { @@ -9777,7 +9777,7 @@ "type": "tidelift" } ], - "time": "2022-06-26T16:57:59+00:00" + "time": "2023-02-01T08:18:48+00:00" }, { "name": "symfony/mime", @@ -12647,16 +12647,16 @@ }, { "name": "composer/composer", - "version": "2.6.6", + "version": "2.7.1", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "683557bd2466072777309d039534bb1332d0dda5" + "reference": "aaf6ed5ccd27c23f79a545e351b4d7842a99d0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/683557bd2466072777309d039534bb1332d0dda5", - "reference": "683557bd2466072777309d039534bb1332d0dda5", + "url": "https://api.github.com/repos/composer/composer/zipball/aaf6ed5ccd27c23f79a545e351b4d7842a99d0bc", + "reference": "aaf6ed5ccd27c23f79a545e351b4d7842a99d0bc", "shasum": "" }, "require": { @@ -12674,7 +12674,7 @@ "seld/jsonlint": "^1.4", "seld/phar-utils": "^1.2", "seld/signal-handler": "^2.0", - "symfony/console": "^5.4.11 || ^6.0.11", + "symfony/console": "^5.4.11 || ^6.0.11 || ^7", "symfony/filesystem": "^5.4 || ^6.0 || ^7", "symfony/finder": "^5.4 || ^6.0 || ^7", "symfony/polyfill-php73": "^1.24", @@ -12688,7 +12688,7 @@ "phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-strict-rules": "^1", "phpstan/phpstan-symfony": "^1.2.10", - "symfony/phpunit-bridge": "^6.0 || ^7" + "symfony/phpunit-bridge": "^6.4.1 || ^7.0.1" }, "suggest": { "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", @@ -12701,7 +12701,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.6-dev" + "dev-main": "2.7-dev" }, "phpstan": { "includes": [ @@ -12741,7 +12741,7 @@ "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/composer/issues", "security": "https://github.com/composer/composer/security/policy", - "source": "https://github.com/composer/composer/tree/2.6.6" + "source": "https://github.com/composer/composer/tree/2.7.1" }, "funding": [ { @@ -12757,7 +12757,7 @@ "type": "tidelift" } ], - "time": "2023-12-08T17:32:26+00:00" + "time": "2024-02-09T14:26:28+00:00" }, { "name": "composer/metadata-minifier", @@ -17088,5 +17088,5 @@ "ext-pdo": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/config/version.php b/config/version.php index 08dc9f581c..e00ceb97d9 100644 --- a/config/version.php +++ b/config/version.php @@ -1,10 +1,10 @@ 'v6.3.0', - 'full_app_version' => 'v6.3.0 - build 12490-g9136415bb', - 'build_version' => '12490', + 'app_version' => 'v6.3.1', + 'full_app_version' => 'v6.3.1 - build 12672-g00cea3eb3', + 'build_version' => '12672', 'prerelease_version' => '', - 'hash_version' => 'g9136415bb', - 'full_hash' => 'v6.3.0-729-g9136415bb', - 'branch' => 'develop', + 'hash_version' => 'g00cea3eb3', + 'full_hash' => 'v6.3.1-180-g00cea3eb3', + 'branch' => 'master', ); \ No newline at end of file diff --git a/database/factories/AccessoryFactory.php b/database/factories/AccessoryFactory.php index 4066526770..ce0d60cc18 100644 --- a/database/factories/AccessoryFactory.php +++ b/database/factories/AccessoryFactory.php @@ -8,6 +8,7 @@ use App\Models\Location; use App\Models\Manufacturer; use App\Models\Supplier; use App\Models\User; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\Factory; class AccessoryFactory extends Factory @@ -140,4 +141,16 @@ class AccessoryFactory extends Factory $accessory->category->update(['require_acceptance' => 1]); }); } + + public function checkedOutToUser(User $user = null) + { + return $this->afterCreating(function (Accessory $accessory) use ($user) { + $accessory->users()->attach($accessory->id, [ + 'accessory_id' => $accessory->id, + 'created_at' => Carbon::now(), + 'user_id' => 1, + 'assigned_to' => $user->id ?? User::factory()->create()->id, + ]); + }); + } } diff --git a/package-lock.json b/package-lock.json index 6db30f8651..ab896b09fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1565,9 +1565,9 @@ } }, "@types/eslint": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.1.tgz", - "integrity": "sha512-18PLWRzhy9glDQp3+wOgfLYRWlhgX0azxgJ63rdpoUHyrC9z0f5CkFburjQx4uD7ZCruw85ZtMt6K+L+R8fLJQ==", + "version": "8.56.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz", + "integrity": "sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==", "requires": { "@types/estree": "*", "@types/json-schema": "*" @@ -2350,9 +2350,9 @@ } }, "alpinejs": { - "version": "3.13.3", - "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.3.tgz", - "integrity": "sha512-WZ6WQjkAOl+WdW/jukzNHq9zHFDNKmkk/x6WF7WdyNDD6woinrfXCVsZXm0galjbco+pEpYmJLtwlZwcOfIVdg==", + "version": "3.13.5", + "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.5.tgz", + "integrity": "sha512-1d2XeNGN+Zn7j4mUAKXtAgdc4/rLeadyTMWeJGXF5DzwawPBxwTiBhFFm6w/Ei8eJxUZeyNWWSD9zknfdz1kEw==", "requires": { "@vue/reactivity": "~3.1.1" } @@ -3611,6 +3611,7 @@ "version": "4.19.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "dev": true, "requires": { "caniuse-lite": "^1.0.30001286", "electron-to-chromium": "^1.4.17", @@ -3731,7 +3732,8 @@ "caniuse-lite": { "version": "1.0.30001292", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001292.tgz", - "integrity": "sha512-jnT4Tq0Q4ma+6nncYQVe7d73kmDmE9C3OGTx3MvW7lBM/eY1S1DZTMBON7dqV481RhNiS5OxD7k9JQvmDOTirw==" + "integrity": "sha512-jnT4Tq0Q4ma+6nncYQVe7d73kmDmE9C3OGTx3MvW7lBM/eY1S1DZTMBON7dqV481RhNiS5OxD7k9JQvmDOTirw==", + "dev": true }, "canvg": { "version": "3.0.10", @@ -4931,7 +4933,8 @@ "electron-to-chromium": { "version": "1.4.28", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.28.tgz", - "integrity": "sha512-Gzbf0wUtKfyPaqf0Plz+Ctinf9eQIzxEqBHwSvbGfeOm9GMNdLxyu1dNiCUfM+x6r4BE0xUJNh3Nmg9gfAtTmg==" + "integrity": "sha512-Gzbf0wUtKfyPaqf0Plz+Ctinf9eQIzxEqBHwSvbGfeOm9GMNdLxyu1dNiCUfM+x6r4BE0xUJNh3Nmg9gfAtTmg==", + "dev": true }, "elliptic": { "version": "6.5.4", @@ -17418,7 +17421,8 @@ "node-releases": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==" + "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "dev": true }, "normalize-path": { "version": "3.0.0", @@ -20097,18 +20101,18 @@ "dev": true }, "webpack": { - "version": "5.89.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", - "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "version": "5.90.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz", + "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==", "requires": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", + "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.11.5", "@webassemblyjs/wasm-edit": "^1.11.5", "@webassemblyjs/wasm-parser": "^1.11.5", "acorn": "^8.7.1", "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.15.0", "es-module-lexer": "^1.2.1", @@ -20122,15 +20126,15 @@ "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", + "terser-webpack-plugin": "^5.3.10", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, "dependencies": { "@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" }, "@jridgewell/sourcemap-codec": { "version": "1.4.15", @@ -20138,9 +20142,9 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", "requires": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -20151,16 +20155,42 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, + "browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "requires": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + } + }, + "caniuse-lite": { + "version": "1.0.30001587", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz", + "integrity": "sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA==" + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, + "electron-to-chromium": { + "version": "1.4.670", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.670.tgz", + "integrity": "sha512-hcijYOWjOtjKrKPtNA6tuLlA/bTLO3heFG8pQA6mLpq7dRydSWicXova5lyxDzp1iVJaYhK7J2OQlGE52KYn7A==" + }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, + "node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + }, "schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -20172,17 +20202,17 @@ } }, "serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "requires": { "randombytes": "^2.1.0" } }, "terser": { - "version": "5.26.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.26.0.tgz", - "integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==", + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", + "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==", "requires": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -20202,6 +20232,15 @@ "terser": "^5.26.0" } }, + "update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, "webpack-sources": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", diff --git a/package.json b/package.json index e54390ee90..7a7b20e906 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "acorn-import-assertions": "^1.9.0", "admin-lte": "^2.4.18", "ajv": "^6.12.6", - "alpinejs": "^3.13.3", + "alpinejs": "^3.13.5", "blueimp-file-upload": "^9.34.0", "bootstrap": "^3.4.1", "bootstrap-colorpicker": "^2.5.3", @@ -59,6 +59,6 @@ "tableexport.jquery.plugin": "1.28.0", "tether": "^1.4.0", "vue-resource": "^1.5.2", - "webpack": "^5.89.0" + "webpack": "^5.90.0" } } diff --git a/public/css/build/AdminLTE.css b/public/css/build/AdminLTE.css index ccebc2f518..a5db4de5c9 100644 Binary files a/public/css/build/AdminLTE.css and b/public/css/build/AdminLTE.css differ diff --git a/public/css/build/app.css b/public/css/build/app.css index 23a455db61..394c114a5d 100644 Binary files a/public/css/build/app.css and b/public/css/build/app.css differ diff --git a/public/css/build/overrides.css b/public/css/build/overrides.css index 1630ba1df9..9675f14e55 100644 Binary files a/public/css/build/overrides.css and b/public/css/build/overrides.css differ diff --git a/public/css/dist/all.css b/public/css/dist/all.css index a05999fad9..34826a963e 100644 Binary files a/public/css/dist/all.css and b/public/css/dist/all.css differ diff --git a/public/css/dist/bootstrap-table.css b/public/css/dist/bootstrap-table.css index 3a0ccc65c7..496901b537 100644 Binary files a/public/css/dist/bootstrap-table.css and b/public/css/dist/bootstrap-table.css differ diff --git a/public/css/dist/signature-pad.min.css b/public/css/dist/signature-pad.min.css index 218b9c2365..7065929572 100644 Binary files a/public/css/dist/signature-pad.min.css and b/public/css/dist/signature-pad.min.css differ diff --git a/public/css/dist/skins/skin-black-dark.css b/public/css/dist/skins/skin-black-dark.css index f12332df6d..bdab8393c5 100644 Binary files a/public/css/dist/skins/skin-black-dark.css and b/public/css/dist/skins/skin-black-dark.css differ diff --git a/public/css/dist/skins/skin-black-dark.min.css b/public/css/dist/skins/skin-black-dark.min.css index f12332df6d..fa7c210124 100644 Binary files a/public/css/dist/skins/skin-black-dark.min.css and b/public/css/dist/skins/skin-black-dark.min.css differ diff --git a/public/css/dist/skins/skin-black.css b/public/css/dist/skins/skin-black.css index ab4d845e2a..2d3d0edc3b 100644 Binary files a/public/css/dist/skins/skin-black.css and b/public/css/dist/skins/skin-black.css differ diff --git a/public/css/dist/skins/skin-black.min.css b/public/css/dist/skins/skin-black.min.css index ab4d845e2a..5a5decc906 100644 Binary files a/public/css/dist/skins/skin-black.min.css and b/public/css/dist/skins/skin-black.min.css differ diff --git a/public/css/dist/skins/skin-blue-dark.css b/public/css/dist/skins/skin-blue-dark.css index c4f2672931..9c1e130515 100644 Binary files a/public/css/dist/skins/skin-blue-dark.css and b/public/css/dist/skins/skin-blue-dark.css differ diff --git a/public/css/dist/skins/skin-blue-dark.min.css b/public/css/dist/skins/skin-blue-dark.min.css index c4f2672931..a70912484e 100644 Binary files a/public/css/dist/skins/skin-blue-dark.min.css and b/public/css/dist/skins/skin-blue-dark.min.css differ diff --git a/public/css/dist/skins/skin-blue.css b/public/css/dist/skins/skin-blue.css index cac9000174..cdc62766ec 100644 Binary files a/public/css/dist/skins/skin-blue.css and b/public/css/dist/skins/skin-blue.css differ diff --git a/public/css/dist/skins/skin-blue.min.css b/public/css/dist/skins/skin-blue.min.css index cac9000174..09b0bba755 100644 Binary files a/public/css/dist/skins/skin-blue.min.css and b/public/css/dist/skins/skin-blue.min.css differ diff --git a/public/css/dist/skins/skin-contrast.css b/public/css/dist/skins/skin-contrast.css index 50dfc577e2..8f5c99c013 100644 Binary files a/public/css/dist/skins/skin-contrast.css and b/public/css/dist/skins/skin-contrast.css differ diff --git a/public/css/dist/skins/skin-contrast.min.css b/public/css/dist/skins/skin-contrast.min.css index 50dfc577e2..25ebe1f277 100644 Binary files a/public/css/dist/skins/skin-contrast.min.css and b/public/css/dist/skins/skin-contrast.min.css differ diff --git a/public/css/dist/skins/skin-green-dark.css b/public/css/dist/skins/skin-green-dark.css index e024040c4e..7c035a9ec9 100644 Binary files a/public/css/dist/skins/skin-green-dark.css and b/public/css/dist/skins/skin-green-dark.css differ diff --git a/public/css/dist/skins/skin-green-dark.min.css b/public/css/dist/skins/skin-green-dark.min.css index e024040c4e..12bcb66477 100644 Binary files a/public/css/dist/skins/skin-green-dark.min.css and b/public/css/dist/skins/skin-green-dark.min.css differ diff --git a/public/css/dist/skins/skin-green.css b/public/css/dist/skins/skin-green.css index fe0b851609..cd113d5e54 100644 Binary files a/public/css/dist/skins/skin-green.css and b/public/css/dist/skins/skin-green.css differ diff --git a/public/css/dist/skins/skin-green.min.css b/public/css/dist/skins/skin-green.min.css index fe0b851609..620d48e3f4 100644 Binary files a/public/css/dist/skins/skin-green.min.css and b/public/css/dist/skins/skin-green.min.css differ diff --git a/public/css/dist/skins/skin-orange-dark.css b/public/css/dist/skins/skin-orange-dark.css index 1ec2c0701a..b52b17dd02 100644 Binary files a/public/css/dist/skins/skin-orange-dark.css and b/public/css/dist/skins/skin-orange-dark.css differ diff --git a/public/css/dist/skins/skin-orange-dark.min.css b/public/css/dist/skins/skin-orange-dark.min.css index 1ec2c0701a..8c0492a9b5 100644 Binary files a/public/css/dist/skins/skin-orange-dark.min.css and b/public/css/dist/skins/skin-orange-dark.min.css differ diff --git a/public/css/dist/skins/skin-orange.css b/public/css/dist/skins/skin-orange.css index b26415b6a3..411d4994ac 100644 Binary files a/public/css/dist/skins/skin-orange.css and b/public/css/dist/skins/skin-orange.css differ diff --git a/public/css/dist/skins/skin-orange.min.css b/public/css/dist/skins/skin-orange.min.css index b26415b6a3..af01a70cff 100644 Binary files a/public/css/dist/skins/skin-orange.min.css and b/public/css/dist/skins/skin-orange.min.css differ diff --git a/public/css/dist/skins/skin-purple-dark.css b/public/css/dist/skins/skin-purple-dark.css index cec9819d7d..33a21545b3 100644 Binary files a/public/css/dist/skins/skin-purple-dark.css and b/public/css/dist/skins/skin-purple-dark.css differ diff --git a/public/css/dist/skins/skin-purple-dark.min.css b/public/css/dist/skins/skin-purple-dark.min.css index cec9819d7d..0c0a5bd957 100644 Binary files a/public/css/dist/skins/skin-purple-dark.min.css and b/public/css/dist/skins/skin-purple-dark.min.css differ diff --git a/public/css/dist/skins/skin-purple.css b/public/css/dist/skins/skin-purple.css index d4f67fee9b..344cbfce10 100644 Binary files a/public/css/dist/skins/skin-purple.css and b/public/css/dist/skins/skin-purple.css differ diff --git a/public/css/dist/skins/skin-purple.min.css b/public/css/dist/skins/skin-purple.min.css index d4f67fee9b..8a89eae1a6 100644 Binary files a/public/css/dist/skins/skin-purple.min.css and b/public/css/dist/skins/skin-purple.min.css differ diff --git a/public/css/dist/skins/skin-red-dark.css b/public/css/dist/skins/skin-red-dark.css index 17d495cbbb..b92ad70dfa 100644 Binary files a/public/css/dist/skins/skin-red-dark.css and b/public/css/dist/skins/skin-red-dark.css differ diff --git a/public/css/dist/skins/skin-red-dark.min.css b/public/css/dist/skins/skin-red-dark.min.css index 17d495cbbb..afe056285f 100644 Binary files a/public/css/dist/skins/skin-red-dark.min.css and b/public/css/dist/skins/skin-red-dark.min.css differ diff --git a/public/css/dist/skins/skin-red.css b/public/css/dist/skins/skin-red.css index 0dc3658056..c6167ad739 100644 Binary files a/public/css/dist/skins/skin-red.css and b/public/css/dist/skins/skin-red.css differ diff --git a/public/css/dist/skins/skin-red.min.css b/public/css/dist/skins/skin-red.min.css index 0dc3658056..4f73db5918 100644 Binary files a/public/css/dist/skins/skin-red.min.css and b/public/css/dist/skins/skin-red.min.css differ diff --git a/public/css/dist/skins/skin-yellow-dark.css b/public/css/dist/skins/skin-yellow-dark.css index 09babaa6ab..aea34738e2 100644 Binary files a/public/css/dist/skins/skin-yellow-dark.css and b/public/css/dist/skins/skin-yellow-dark.css differ diff --git a/public/css/dist/skins/skin-yellow-dark.min.css b/public/css/dist/skins/skin-yellow-dark.min.css index 09babaa6ab..73b2bba0df 100644 Binary files a/public/css/dist/skins/skin-yellow-dark.min.css and b/public/css/dist/skins/skin-yellow-dark.min.css differ diff --git a/public/css/dist/skins/skin-yellow.css b/public/css/dist/skins/skin-yellow.css index 8aef4cb90e..5424b7f75d 100644 Binary files a/public/css/dist/skins/skin-yellow.css and b/public/css/dist/skins/skin-yellow.css differ diff --git a/public/css/dist/skins/skin-yellow.min.css b/public/css/dist/skins/skin-yellow.min.css index 8aef4cb90e..f64159f334 100644 Binary files a/public/css/dist/skins/skin-yellow.min.css and b/public/css/dist/skins/skin-yellow.min.css differ diff --git a/public/js/build/app.js b/public/js/build/app.js index 67fab4f67b..bd39438a16 100644 Binary files a/public/js/build/app.js and b/public/js/build/app.js differ diff --git a/public/js/build/vendor.js b/public/js/build/vendor.js index f65d4fdd87..15466571b5 100644 Binary files a/public/js/build/vendor.js and b/public/js/build/vendor.js differ diff --git a/public/js/dist/all-defer.js b/public/js/dist/all-defer.js index 53e56a8a67..d41d5cc7e6 100644 Binary files a/public/js/dist/all-defer.js and b/public/js/dist/all-defer.js differ diff --git a/public/js/dist/all.js b/public/js/dist/all.js index 9ed724f7a1..dd2904ba3e 100644 Binary files a/public/js/dist/all.js and b/public/js/dist/all.js differ diff --git a/public/js/dist/bootstrap-table.js b/public/js/dist/bootstrap-table.js index 6dfb14e51a..5161d15d12 100644 Binary files a/public/js/dist/bootstrap-table.js and b/public/js/dist/bootstrap-table.js differ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 8c20f3cc48..bfdc4699c8 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,24 +1,24 @@ { - "/js/build/app.js": "/js/build/app.js?id=ea5f3edebafdb29b616d23fa89106080", - "/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=f677207c6cf9678eb539abecb408c374", - "/css/build/overrides.css": "/css/build/overrides.css?id=9a69d65b9f2b35e6d17c6a72e76424d5", - "/css/build/app.css": "/css/build/app.css?id=5a516232bc9f1488514577cf3c8108eb", - "/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=dc383f8560a8d4adb51d44fb4043e03b", - "/css/dist/skins/skin-orange.css": "/css/dist/skins/skin-orange.css?id=6f0563e726c2fe4fab4026daaa5bfdf2", - "/css/dist/skins/skin-orange-dark.css": "/css/dist/skins/skin-orange-dark.css?id=e6e53eef152bba01a4c666a4d8b01117", - "/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=07273f6ca3c698a39e8fc2075af4fa07", - "/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=c1f33574ecb9d3e69d9b8fe5bd68e101", - "/css/dist/skins/skin-yellow.css": "/css/dist/skins/skin-yellow.css?id=7b315b9612b8fde8f9c5b0ddb6bba690", - "/css/dist/skins/skin-purple-dark.css": "/css/dist/skins/skin-purple-dark.css?id=7d92dea45d94be7e1d4e427c728d335d", - "/css/dist/skins/skin-purple.css": "/css/dist/skins/skin-purple.css?id=6fe68325d5356197672c27bc77cedcb4", - "/css/dist/skins/skin-red-dark.css": "/css/dist/skins/skin-red-dark.css?id=8ca888bbc050d9680cbb65021382acba", - "/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=b061bb141af3bdb6280c6ee772cf8f4f", - "/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=76482123f6c70e866d6b971ba91de7bb", - "/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=d419cb63a12dc175d71645c876bfc2ab", - "/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=0a82a6ae6bb4e58fe62d162c4fb50397", - "/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=da6c7997d9de2f8329142399f0ce50da", - "/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=44bf834f2110504a793dadec132a5898", - "/css/dist/all.css": "/css/dist/all.css?id=ebd6663d2f61487038c9947111be2c73", + "/js/build/app.js": "/js/build/app.js?id=cad71122a8a3f0cd6c5960e95f4f0f8a", + "/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=392cc93cfc0be0349bab9697669dd091", + "/css/build/overrides.css": "/css/build/overrides.css?id=6a7f37afafaaf9ccea99a7391cdf02b2", + "/css/build/app.css": "/css/build/app.css?id=ea3875faceb1d09c162d00fbf5b4df57", + "/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=f25c77ed07053646a42e9c19923d24fa", + "/css/dist/skins/skin-orange.css": "/css/dist/skins/skin-orange.css?id=268041e902b019730c23ee3875838005", + "/css/dist/skins/skin-orange-dark.css": "/css/dist/skins/skin-orange-dark.css?id=d409d9b1a3b69247df8b98941ba06e33", + "/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=4a9e8c5e7b09506fa3e3a3f42849e07f", + "/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=21fef066e0bb1b02fd83fcb6694fad5f", + "/css/dist/skins/skin-yellow.css": "/css/dist/skins/skin-yellow.css?id=fc7adb943668ac69fe4b646625a7571f", + "/css/dist/skins/skin-purple-dark.css": "/css/dist/skins/skin-purple-dark.css?id=9f944e8021781af1ce45d27765d1c0c2", + "/css/dist/skins/skin-purple.css": "/css/dist/skins/skin-purple.css?id=cf6c8c340420724b02d6e787ef9bded5", + "/css/dist/skins/skin-red-dark.css": "/css/dist/skins/skin-red-dark.css?id=7f0eb9e355b36b41c61c3af3b4d41143", + "/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=6cf460bed48ab738041f60231a3f005a", + "/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=1f33ca3d860461c1127ec465ab3ebb6b", + "/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=0ed42b67f9b02a74815e885bfd9e3f66", + "/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=b48f4d8af0e1ca5621c161e93951109f", + "/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=f0fbbb0ac729ea092578fb05ca615460", + "/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=b9a74ec0cd68f83e7480d5ae39919beb", + "/css/dist/all.css": "/css/dist/all.css?id=2ef1965d45a0a72336dd8e9b93f82d80", "/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7", "/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7", "/css/webfonts/fa-brands-400.ttf": "/css/webfonts/fa-brands-400.ttf?id=69e5d8e4e818f05fd882cceb758d1eba", @@ -32,21 +32,21 @@ "/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=afa255bf30b2a7c11a97e3165128d183", "/js/build/vendor.js": "/js/build/vendor.js?id=a2b971da417306a63385c8098acfe4af", "/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=29340c70d13855fa0165cd4d799c6f5b", - "/js/dist/all.js": "/js/dist/all.js?id=13bdb521e0c745d7f81dae3fb110b650", - "/js/dist/all-defer.js": "/js/dist/all-defer.js?id=7f9a130eda6916eaa32a0a57e81918f3", - "/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=0a82a6ae6bb4e58fe62d162c4fb50397", - "/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=d419cb63a12dc175d71645c876bfc2ab", - "/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=76482123f6c70e866d6b971ba91de7bb", - "/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=b061bb141af3bdb6280c6ee772cf8f4f", - "/css/dist/skins/skin-blue.min.css": "/css/dist/skins/skin-blue.min.css?id=f677207c6cf9678eb539abecb408c374", - "/css/dist/skins/skin-blue-dark.min.css": "/css/dist/skins/skin-blue-dark.min.css?id=07273f6ca3c698a39e8fc2075af4fa07", - "/css/dist/skins/skin-yellow.min.css": "/css/dist/skins/skin-yellow.min.css?id=7b315b9612b8fde8f9c5b0ddb6bba690", - "/css/dist/skins/skin-yellow-dark.min.css": "/css/dist/skins/skin-yellow-dark.min.css?id=c1f33574ecb9d3e69d9b8fe5bd68e101", - "/css/dist/skins/skin-red.min.css": "/css/dist/skins/skin-red.min.css?id=44bf834f2110504a793dadec132a5898", - "/css/dist/skins/skin-red-dark.min.css": "/css/dist/skins/skin-red-dark.min.css?id=8ca888bbc050d9680cbb65021382acba", - "/css/dist/skins/skin-purple.min.css": "/css/dist/skins/skin-purple.min.css?id=6fe68325d5356197672c27bc77cedcb4", - "/css/dist/skins/skin-purple-dark.min.css": "/css/dist/skins/skin-purple-dark.min.css?id=7d92dea45d94be7e1d4e427c728d335d", - "/css/dist/skins/skin-orange.min.css": "/css/dist/skins/skin-orange.min.css?id=6f0563e726c2fe4fab4026daaa5bfdf2", - "/css/dist/skins/skin-orange-dark.min.css": "/css/dist/skins/skin-orange-dark.min.css?id=e6e53eef152bba01a4c666a4d8b01117", - "/css/dist/skins/skin-contrast.min.css": "/css/dist/skins/skin-contrast.min.css?id=da6c7997d9de2f8329142399f0ce50da" + "/js/dist/all.js": "/js/dist/all.js?id=a9bde3f908f73634c0d1b4cd3f835092", + "/js/dist/all-defer.js": "/js/dist/all-defer.js?id=19ccc62a8f1ea103dede4808837384d4", + "/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=b48f4d8af0e1ca5621c161e93951109f", + "/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=0ed42b67f9b02a74815e885bfd9e3f66", + "/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=1f33ca3d860461c1127ec465ab3ebb6b", + "/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=6cf460bed48ab738041f60231a3f005a", + "/css/dist/skins/skin-blue.min.css": "/css/dist/skins/skin-blue.min.css?id=392cc93cfc0be0349bab9697669dd091", + "/css/dist/skins/skin-blue-dark.min.css": "/css/dist/skins/skin-blue-dark.min.css?id=4a9e8c5e7b09506fa3e3a3f42849e07f", + "/css/dist/skins/skin-yellow.min.css": "/css/dist/skins/skin-yellow.min.css?id=fc7adb943668ac69fe4b646625a7571f", + "/css/dist/skins/skin-yellow-dark.min.css": "/css/dist/skins/skin-yellow-dark.min.css?id=21fef066e0bb1b02fd83fcb6694fad5f", + "/css/dist/skins/skin-red.min.css": "/css/dist/skins/skin-red.min.css?id=b9a74ec0cd68f83e7480d5ae39919beb", + "/css/dist/skins/skin-red-dark.min.css": "/css/dist/skins/skin-red-dark.min.css?id=7f0eb9e355b36b41c61c3af3b4d41143", + "/css/dist/skins/skin-purple.min.css": "/css/dist/skins/skin-purple.min.css?id=cf6c8c340420724b02d6e787ef9bded5", + "/css/dist/skins/skin-purple-dark.min.css": "/css/dist/skins/skin-purple-dark.min.css?id=9f944e8021781af1ce45d27765d1c0c2", + "/css/dist/skins/skin-orange.min.css": "/css/dist/skins/skin-orange.min.css?id=268041e902b019730c23ee3875838005", + "/css/dist/skins/skin-orange-dark.min.css": "/css/dist/skins/skin-orange-dark.min.css?id=d409d9b1a3b69247df8b98941ba06e33", + "/css/dist/skins/skin-contrast.min.css": "/css/dist/skins/skin-contrast.min.css?id=f0fbbb0ac729ea092578fb05ca615460" } diff --git a/resources/lang/en-US/general.php b/resources/lang/en-US/general.php index 0db52859ff..6e1663b590 100644 --- a/resources/lang/en-US/general.php +++ b/resources/lang/en-US/general.php @@ -182,6 +182,7 @@ return [ 'lock_passwords' => 'This field value will not be saved in a demo installation.', 'feature_disabled' => 'This feature has been disabled for the demo installation.', 'location' => 'Location', + 'location_plural' => 'Location|Locations', 'locations' => 'Locations', 'logo_size' => 'Square logos look best with Logo + Text. Logo maximum display size is 50px high x 500px wide. ', 'logout' => 'Logout', @@ -443,7 +444,6 @@ return [ 'sample_value' => 'Sample Value', 'no_headers' => 'No Columns Found', 'error_in_import_file' => 'There was an error reading the CSV file: :error', - 'percent_complete' => ':percent % Complete', 'errors_importing' => 'Some Errors occurred while importing: ', 'warning' => 'WARNING: :warning', 'success_redirecting' => '"Success... Redirecting.', @@ -459,6 +459,7 @@ return [ 'no_autoassign_licenses_help' => 'Do not include user for bulk-assigning through the license UI or cli tools.', 'modal_confirm_generic' => 'Are you sure?', 'cannot_be_deleted' => 'This item cannot be deleted', + 'cannot_be_edited' => 'This item cannot be edited.', 'undeployable_tooltip' => 'This item cannot be checked out. Check the quantity remaining.', 'serial_number' => 'Serial Number', 'item_notes' => ':item Notes', @@ -501,5 +502,17 @@ return [ 'action_source' => 'Action Source', 'or' => 'or', 'url' => 'URL', + 'edit_fieldset' => 'Edit fieldset fields and options', + 'bulk' => [ + 'delete' => + [ + 'header' => 'Bulk Delete :object_type', + 'warn' => 'You are about to delete one :object_type|You are about to delete :count :object_type', + 'success' => ':object_type successfully deleted|Successfully deleted :count :object_type', + 'error' => 'Could not delete :object_type', + 'nothing_selected' => 'No :object_type selected - nothing to do', + 'partial' => 'Deleted :success_count :object_type, but :error_count :object_type could not be deleted', + ], + ], ]; diff --git a/resources/views/components/view.blade.php b/resources/views/components/view.blade.php index 6c3c04511a..bbb9b67097 100644 --- a/resources/views/components/view.blade.php +++ b/resources/views/components/view.blade.php @@ -279,7 +279,7 @@
- {!! nl2br(e($component->notes)) !!} + {!! nl2br(Helper::parseEscapedMarkedownInline($component->notes)) !!}
@endif diff --git a/resources/views/custom_fields/fieldsets/view.blade.php b/resources/views/custom_fields/fieldsets/view.blade.php index ec25697e1b..4ac97586f1 100644 --- a/resources/views/custom_fields/fieldsets/view.blade.php +++ b/resources/views/custom_fields/fieldsets/view.blade.php @@ -77,7 +77,7 @@ @can('update', $custom_fieldset)
@csrf - +
@endcan @@ -90,35 +90,34 @@ {{ Form::open(['route' => ["fieldsets.associate",$custom_fieldset->id], - 'class'=>'form-horizontal', + 'class'=>'form-inline', 'id' => 'ordering']) }} -
+
- {{ Form::select("field_id",$custom_fields_list,"",['aria-label'=>'field_id', 'class'=>'select2']) }} + {{ Form::select("field_id",$custom_fields_list,"",['aria-label'=>'field_id', 'class'=>'select2', 'style' => 'min-width:400px;']) }}
-
- - - -
-