diff --git a/.all-contributorsrc b/.all-contributorsrc index c10b08018..3684e2e26 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -2970,6 +2970,15 @@ "contributions": [ "code" ] + }, + { + "login": "mmanjos", + "name": "mmanjos", + "avatar_url": "https://avatars.githubusercontent.com/u/3483684?v=4", + "profile": "https://github.com/mmanjos", + "contributions": [ + "code" + ] } ] } diff --git a/.env.docker b/.env.docker index 8c47c801e..87897b10d 100644 --- a/.env.docker +++ b/.env.docker @@ -159,6 +159,7 @@ LOG_CHANNEL=stderr LOG_MAX_DAYS=10 APP_LOCKED=false APP_CIPHER=AES-256-CBC +APP_FORCE_TLS=false GOOGLE_MAPS_API= LDAP_MEM_LIM=500M LDAP_TIME_LIM=600 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a82946161..611c1171b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,9 +25,8 @@ jobs: fail-fast: false matrix: php-version: - - "7.4" - - "8.0" - - "8.1.1" + - "8.1" + - "8.2" name: PHP ${{ matrix.php-version }} @@ -58,11 +57,17 @@ jobs: - name: Install Dependencies run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist - - name: Generate key - run: php artisan key:generate - - - name: Directory Permissions - run: chmod -R 777 storage bootstrap/cache + - name: Setup Laravel + env: + DB_CONNECTION: mysql + DB_DATABASE: snipeit + DB_PORT: ${{ job.services.mysql.ports[3306] }} + DB_USERNAME: root + run: | + php artisan key:generate + php artisan migrate --force + php artisan passport:install + chmod -R 777 storage bootstrap/cache - name: Execute tests (Unit and Feature tests) via PHPUnit env: diff --git a/README.md b/README.md index 3a74120b3..71aba22b5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade) -[![All Contributors](https://img.shields.io/badge/all_contributors-327-orange.svg?style=flat-square)](#contributors) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk) [![huntr](https://cdn.huntr.dev/huntr_security_badge_mono.svg)](https://huntr.dev) +[![All Contributors](https://img.shields.io/badge/all_contributors-328-orange.svg?style=flat-square)](#contributors) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk) [![huntr](https://cdn.huntr.dev/huntr_security_badge_mono.svg)](https://huntr.dev) ## Snipe-IT - Open Source Asset Management System @@ -145,7 +145,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken | [
Chris Hartjes](http://www.littlehart.net/atthekeyboard)
[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [
geo-chen](https://github.com/geo-chen)
[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [
Phan Nguyen](https://github.com/nh314)
[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [
Iisakki Jaakkola](https://github.com/StarlessNights)
[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [
Ikko Ashimine](https://bandism.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [
Lukas Fehling](https://github.com/lukasfehling)
[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [
Fernando Almeida](https://github.com/fernando-almeida)
[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") | | [
akemidx](https://github.com/akemidx)
[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [
Oguz Bilgic](http://oguz.site)
[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [
Scooter Crawford](https://github.com/scoo73r)
[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [
subdriven](https://github.com/subdriven)
[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [
Andrew Savinykh](https://github.com/AndrewSav)
[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [
Tadayuki Onishi](https://kenchan0130.github.io)
[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [
Florian](https://github.com/floschoepfer)
[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") | | [
Spencer Long](http://spencerlong.com)
[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") | [
Marcus Moore](https://github.com/marcusmoore)
[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [
Martin Meredith](https://github.com/Mezzle)
| [
dboth](http://dboth.de)
[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [
Zachary Fleck](https://github.com/zacharyfleck)
[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [
VIKAAS-A](https://github.com/vikaas-cyper)
[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [
Abdul Kareem](https://github.com/ak-piracha)
[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") | -| [
NojoudAlshehri](https://github.com/NojoudAlshehri)
[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [
Stefan Stidl](https://github.com/stefanstidlffg)
[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [
Quentin Aymard](https://github.com/qay21)
[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [
Grant Le Roux](https://github.com/cram42)
[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [
Bogdan](http://@singrity)
[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | +| [
NojoudAlshehri](https://github.com/NojoudAlshehri)
[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [
Stefan Stidl](https://github.com/stefanstidlffg)
[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [
Quentin Aymard](https://github.com/qay21)
[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [
Grant Le Roux](https://github.com/cram42)
[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [
Bogdan](http://@singrity)
[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [
mmanjos](https://github.com/mmanjos)
[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! diff --git a/app/Console/Commands/LdapSync.php b/app/Console/Commands/LdapSync.php index afe1070dd..1a8521487 100755 --- a/app/Console/Commands/LdapSync.php +++ b/app/Console/Commands/LdapSync.php @@ -18,7 +18,7 @@ class LdapSync extends Command * * @var string */ - protected $signature = 'snipeit:ldap-sync {--location=} {--location_id=} {--base_dn=} {--filter=} {--summary} {--json_summary}'; + protected $signature = 'snipeit:ldap-sync {--location=} {--location_id=*} {--base_dn=} {--filter=} {--summary} {--json_summary}'; /** * The console command description. @@ -83,7 +83,15 @@ class LdapSync extends Command $summary = []; try { - if ($this->option('base_dn') != '') { + if ( $this->option('location_id') != '') { + + foreach($this->option('location_id') as $location_id){ + $location_ou= Location::where('id', '=', $location_id)->value('ldap_ou'); + $search_base = $location_ou; + Log::debug('Importing users from specified location OU: \"'.$search_base.'\".'); + } + } + else if ($this->option('base_dn') != '') { $search_base = $this->option('base_dn'); Log::debug('Importing users from specified base DN: \"'.$search_base.'\".'); } else { @@ -106,21 +114,21 @@ class LdapSync extends Command /* Determine which location to assign users to by default. */ $location = null; // TODO - this would be better called "$default_location", which is more explicit about its purpose + if ($this->option('location') != '') { + if ($location = Location::where('name', '=', $this->option('location'))->first()) { + Log::debug('Location name ' . $this->option('location') . ' passed'); + Log::debug('Importing to ' . $location->name . ' (' . $location->id . ')'); + } + + } elseif ($this->option('location_id') != '') { + foreach($this->option('location_id') as $location_id) { + if ($location = Location::where('id', '=', $location_id)->first()) { + Log::debug('Location ID ' . $location_id . ' passed'); + Log::debug('Importing to ' . $location->name . ' (' . $location->id . ')'); + } - if ($this->option('location') != '') { - if ($location = Location::where('name', '=', $this->option('location'))->first()) { - Log::debug('Location name '.$this->option('location').' passed'); - Log::debug('Importing to '.$location->name.' ('.$location->id.')'); } - - } elseif ($this->option('location_id') != '') { - if ($location = Location::where('id', '=', $this->option('location_id'))->first()) { - Log::debug('Location ID '.$this->option('location_id').' passed'); - Log::debug('Importing to '.$location->name.' ('.$location->id.')'); - } - } - if (! isset($location)) { Log::debug('That location is invalid or a location was not provided, so no location will be assigned by default.'); } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 3d4db9345..e76d8e5da 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -150,6 +150,11 @@ class Handler extends ExceptionHandler return redirect()->guest('login'); } + protected function invalidJson($request, ValidationException $exception) + { + return response()->json(Helper::formatStandardApiResponse('error', null, $exception->errors()), 200); + } + /** * A list of the inputs that are never flashed for validation exceptions. diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php index c2ccda6c8..800a2491d 100644 --- a/app/Helpers/Helper.php +++ b/app/Helpers/Helper.php @@ -73,10 +73,14 @@ class Helper * * @author [A. Gianotto] [] * @since [v3.3] - * @return array + * @return string */ - public static function defaultChartColors($index = 0) + public static function defaultChartColors(int $index = 0) { + if ($index < 0) { + $index = 0; + } + $colors = [ '#008941', '#FF4A46', @@ -349,7 +353,19 @@ class Helper $total_colors = count($colors); if ($index >= $total_colors) { - $index = $index - $total_colors; + + \Log::error('Status label count is '.$index.' and exceeds the allowed count of 266.'); + //patch fix for array key overflow (color count starts at 1, array starts at 0) + $index = $index - $total_colors - 1; + + //constraints to keep result in 0-265 range. This should never be needed, but if something happens + //to create this many status labels and it DOES happen, this will keep it from failing at least. + if($index < 0) { + $index = 0; + } + elseif($index >($total_colors - 1)) { + $index = $total_colors - 1; + } } return $colors[$index]; diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index e49edc4db..954ba3a4d 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Api; use App\Events\CheckoutableCheckedIn; +use App\Http\Requests\StoreAssetRequest; use Illuminate\Support\Facades\Gate; use App\Helpers\Helper; use App\Http\Controllers\Controller; @@ -33,6 +34,7 @@ use TCPDF; use Validator; use Route; + /** * This class controls all actions related to assets for * the Snipe-IT Asset Management application. @@ -48,7 +50,7 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param int $assetId * @since [v4.0] - * @return JsonResponse + * @return \Illuminate\Http\JsonResponse */ public function index(Request $request, $audit = null) { @@ -295,7 +297,7 @@ class AssetsController extends Controller } if ($request->filled('order_number')) { - $assets->where('assets.order_number', '=', $request->get('order_number')); + $assets->where('assets.order_number', '=', strval($request->get('order_number'))); } // This is kinda gross, but we need to do this because the Bootstrap Tables @@ -443,7 +445,7 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param int $assetId * @since [v4.0] - * @return JsonResponse + * @return \Illuminate\Http\JsonResponse */ public function show(Request $request, $id) { @@ -474,7 +476,7 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @since [v4.0.16] * @see \App\Http\Transformers\SelectlistTransformer - * + * @return \Illuminate\Http\JsonResponse */ public function selectlist(Request $request) { @@ -530,12 +532,10 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param \App\Http\Requests\ImageUploadRequest $request * @since [v4.0] - * @return JsonResponse + * @return \Illuminate\Http\JsonResponse */ - public function store(ImageUploadRequest $request) + public function store(StoreAssetRequest $request) { - $this->authorize('create', Asset::class); - $asset = new Asset(); $asset->model()->associate(AssetModel::find((int) $request->get('model_id'))); @@ -639,7 +639,7 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param \App\Http\Requests\ImageUploadRequest $request * @since [v4.0] - * @return JsonResponse + * @return \Illuminate\Http\JsonResponse */ public function update(ImageUploadRequest $request, $id) { @@ -666,10 +666,11 @@ class AssetsController extends Controller $request->offsetSet('image', $request->offsetGet('image_source')); } - $asset = $request->handleImages($asset); + $asset = $request->handleImages($asset); + $model = AssetModel::find($asset->model_id); // Update custom fields - if (($model = AssetModel::find($asset->model_id)) && (isset($model->fieldset))) { + if (($model) && (isset($model->fieldset))) { foreach ($model->fieldset->fields as $field) { if ($request->has($field->db_column)) { if ($field->field_encrypted == '1') { @@ -720,7 +721,7 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param int $assetId * @since [v4.0] - * @return JsonResponse + * @return \Illuminate\Http\JsonResponse */ public function destroy($id) { @@ -749,7 +750,7 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param int $assetId * @since [v5.1.18] - * @return JsonResponse + * @return \Illuminate\Http\JsonResponse */ public function restore(Request $request, $assetId = null) { @@ -789,7 +790,7 @@ class AssetsController extends Controller * @author [N. Butler] * @param string $tag * @since [v6.0.5] - * @return JsonResponse + * @return \Illuminate\Http\JsonResponse */ public function checkoutByTag(AssetCheckoutRequest $request, $tag) { @@ -805,7 +806,7 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param int $assetId * @since [v4.0] - * @return JsonResponse + * @return \Illuminate\Http\JsonResponse */ public function checkout(AssetCheckoutRequest $request, $asset_id) { @@ -889,7 +890,7 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param int $assetId * @since [v4.0] - * @return JsonResponse + * @return \Illuminate\Http\JsonResponse */ public function checkin(Request $request, $asset_id) { @@ -945,7 +946,7 @@ class AssetsController extends Controller * * @author [A. Janes] [] * @since [v6.0] - * @return JsonResponse + * @return \Illuminate\Http\JsonResponse */ public function checkinByTag(Request $request, $tag = null) { @@ -971,7 +972,7 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param int $id * @since [v4.0] - * @return JsonResponse + * @return \Illuminate\Http\JsonResponse */ public function audit(Request $request) @@ -1032,24 +1033,54 @@ class AssetsController extends Controller * * @author [A. Gianotto] [] * @since [v4.0] - * @return JsonResponse + * @return \Illuminate\Http\JsonResponse */ public function requestable(Request $request) { $this->authorize('viewRequestable', Asset::class); + $allowed_columns = [ + 'name', + 'asset_tag', + 'serial', + 'model_number', + 'image', + 'purchase_cost', + 'expected_checkin', + ]; + + $all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load + + foreach ($all_custom_fields as $field) { + $allowed_columns[] = $field->db_column_name(); + } + $assets = Asset::select('assets.*') - ->with('location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo', - 'model.category', 'model.manufacturer', 'model.fieldset', 'supplier') + ->with('location', 'assetstatus', 'assetlog', 'company','assignedTo', + 'model.category', 'model.manufacturer', 'model.fieldset', 'supplier', 'requests') ->requestableAssets(); - $offset = request('offset', 0); - $limit = $request->input('limit', 50); - $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; + + + if ($request->filled('search')) { $assets->TextSearch($request->input('search')); } + // Search custom fields by column name + foreach ($all_custom_fields as $field) { + if ($request->filled($field->db_column_name())) { + $assets->where($field->db_column_name(), '=', $request->input($field->db_column_name())); + } + } + + $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; + $sort_override = str_replace('custom_fields.', '', $request->input('sort')); + + // This handles all the pivot sorting (versus the assets.* fields + // in the allowed_columns array) + $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.created_at'; + switch ($request->input('sort')) { case 'model': $assets->OrderModels($order); @@ -1057,17 +1088,19 @@ class AssetsController extends Controller case 'model_number': $assets->OrderModelNumber($order); break; - case 'category': - $assets->OrderCategory($order); - break; - case 'manufacturer': - $assets->OrderManufacturer($order); + case 'location': + $assets->OrderLocation($order); break; default: - $assets->orderBy('assets.created_at', $order); + $assets->orderBy($column_sort, $order); break; } + + // Make sure the offset and limit are actually integers and do not exceed system limits + $offset = ($request->input('offset') > $assets->count()) ? $assets->count() : app('api_offset_value'); + $limit = app('api_limit_value'); + $total = $assets->count(); $assets = $assets->skip($offset)->take($limit)->get(); diff --git a/app/Http/Controllers/Api/ProfileController.php b/app/Http/Controllers/Api/ProfileController.php index 4f5e3b1bd..ef56ed537 100644 --- a/app/Http/Controllers/Api/ProfileController.php +++ b/app/Http/Controllers/Api/ProfileController.php @@ -11,6 +11,7 @@ use Illuminate\Http\Request; use Laravel\Passport\TokenRepository; use Illuminate\Contracts\Validation\Factory as ValidationFactory; use Illuminate\Support\Facades\Gate; +use App\Models\CustomField; use DB; class ProfileController extends Controller @@ -48,14 +49,23 @@ class ProfileController extends Controller { $checkoutRequests = CheckoutRequest::where('user_id', '=', Auth::user()->id)->get(); - $results = []; + $results = array(); + $show_field = array(); + $showable_fields = array(); $results['total'] = $checkoutRequests->count(); + $all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load + foreach ($all_custom_fields as $field) { + if (($field->field_encrypted=='0') && ($field->show_in_requestable_list=='1')) { + $showable_fields[] = $field->db_column_name(); + } + } + foreach ($checkoutRequests as $checkoutRequest) { // Make sure the asset and request still exist if ($checkoutRequest && $checkoutRequest->itemRequested()) { - $results['rows'][] = [ + $assets = [ 'image' => e($checkoutRequest->itemRequested()->present()->getImageUrl()), 'name' => e($checkoutRequest->itemRequested()->present()->name()), 'type' => e($checkoutRequest->itemType()), @@ -64,7 +74,16 @@ class ProfileController extends Controller 'expected_checkin' => Helper::getFormattedDateObject($checkoutRequest->itemRequested()->expected_checkin, 'datetime'), 'request_date' => Helper::getFormattedDateObject($checkoutRequest->created_at, 'datetime'), ]; + + foreach ($showable_fields as $showable_field_name) { + $show_field['custom_fields.'.$showable_field_name] = $checkoutRequest->itemRequested()->{$showable_field_name}; + } + + // Merge the plain asset data and the custom fields data + $results['rows'][] = array_merge($assets, $show_field); } + + } return $results; diff --git a/app/Http/Controllers/AssetModelsController.php b/app/Http/Controllers/AssetModelsController.php index d7a135dd7..1783b3392 100755 --- a/app/Http/Controllers/AssetModelsController.php +++ b/app/Http/Controllers/AssetModelsController.php @@ -179,9 +179,14 @@ class AssetModelsController extends Controller if ($model->save()) { if ($model->wasChanged('eol')) { - $newEol = $model->eol; - $model->assets()->whereNotNull('purchase_date')->where('eol_explicit', false) - ->update(['asset_eol_date' => DB::raw('DATE_ADD(purchase_date, INTERVAL ' . $newEol . ' MONTH)')]); + if ($model->eol > 0) { + $newEol = $model->eol; + $model->assets()->whereNotNull('purchase_date')->where('eol_explicit', false) + ->update(['asset_eol_date' => DB::raw('DATE_ADD(purchase_date, INTERVAL ' . $newEol . ' MONTH)')]); + } elseif ($model->eol == 0) { + $model->assets()->whereNotNull('purchase_date')->where('eol_explicit', false) + ->update(['asset_eol_date' => DB::raw('null')]); + } } return redirect()->route('models.index')->with('success', trans('admin/models/message.update.success')); } diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index 92922c4cd..28d7906cd 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -137,7 +137,7 @@ class AssetsController extends Controller $asset->warranty_months = request('warranty_months', null); $asset->purchase_cost = request('purchase_cost'); $asset->purchase_date = request('purchase_date', null); - $asset->asset_eol_date = request('asset_eol_date', $asset->present()->eol_date()); + $asset->asset_eol_date = request('asset_eol_date', null); $asset->assigned_to = request('assigned_to', null); $asset->supplier_id = request('supplier_id', null); $asset->requestable = request('requestable', 0); @@ -204,12 +204,8 @@ class AssetsController extends Controller } if ($success) { - // Redirect to the asset listing page - $minutes = 518400; - // dd( $_POST['options']); - // Cookie::queue(Cookie::make('optional_info', json_decode($_POST['options']), $minutes)); return redirect()->route('hardware.index') - ->with('success', trans('admin/hardware/message.create.success')); + ->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset->id), 'id', 'tag' => $asset->asset_tag])); } @@ -309,14 +305,15 @@ class AssetsController extends Controller $asset->warranty_months = $request->input('warranty_months', null); $asset->purchase_cost = $request->input('purchase_cost', null); $asset->purchase_date = $request->input('purchase_date', null); - if ($request->filled('purchase_date') && !$request->filled('asset_eol_date') && $asset->model->eol) { + if ($request->filled('purchase_date') && !$request->filled('asset_eol_date') && ($asset->model->eol > 0)) { $asset->purchase_date = $request->input('purchase_date', null); $asset->asset_eol_date = Carbon::parse($request->input('purchase_date'))->addMonths($asset->model->eol)->format('Y-m-d'); + $asset->eol_explicit = false; } elseif ($request->filled('asset_eol_date')) { $asset->asset_eol_date = $request->input('asset_eol_date', null); $months = Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date); if($asset->model->eol) { - if($months != $asset->model->eol) { + if($months != $asset->model->eol > 0) { $asset->eol_explicit = true; } else { $asset->eol_explicit = false; @@ -324,6 +321,9 @@ class AssetsController extends Controller } else { $asset->eol_explicit = true; } + } elseif (!$request->filled('asset_eol_date') && (($asset->model->eol) == 0)) { + $asset->asset_eol_date = null; + $asset->eol_explicit = false; } $asset->supplier_id = $request->input('supplier_id', null); $asset->expected_checkin = $request->input('expected_checkin', null); diff --git a/app/Http/Controllers/Assets/BulkAssetsController.php b/app/Http/Controllers/Assets/BulkAssetsController.php index 932176286..45ca5bab7 100644 --- a/app/Http/Controllers/Assets/BulkAssetsController.php +++ b/app/Http/Controllers/Assets/BulkAssetsController.php @@ -112,7 +112,8 @@ class BulkAssetsController extends Controller public function update(Request $request) { $this->authorize('update', Asset::class); - $error_bag = []; + $has_errors = 0; + $error_array = array(); // Get the back url from the session and then destroy the session $bulk_back_url = route('hardware.index'); @@ -120,10 +121,9 @@ class BulkAssetsController extends Controller $bulk_back_url = $request->session()->pull('bulk_back_url'); } - - $custom_field_columns = CustomField::all()->pluck('db_column')->toArray(); + $custom_field_columns = CustomField::all()->pluck('db_column')->toArray(); - if(Session::exists('ids')) { + if (Session::exists('ids')) { $assets = Session::get('ids'); } elseif (! $request->filled('ids') || count($request->input('ids')) <= 0) { return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected')); @@ -160,7 +160,6 @@ class BulkAssetsController extends Controller $this->conditionallyAddItem('purchase_date') ->conditionallyAddItem('expected_checkin') - ->conditionallyAddItem('model_id') ->conditionallyAddItem('order_number') ->conditionallyAddItem('requestable') ->conditionallyAddItem('status_id') @@ -187,6 +186,7 @@ class BulkAssetsController extends Controller $this->update_array['purchase_cost'] = $request->input('purchase_cost'); } + if ($request->filled('company_id')) { $this->update_array['company_id'] = $request->input('company_id'); if ($request->input('company_id') == 'clear') { @@ -195,68 +195,108 @@ class BulkAssetsController extends Controller } if ($request->filled('rtd_location_id')) { - $this->update_array['rtd_location_id'] = $request->input('rtd_location_id'); + if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '0')) { + $this->update_array['rtd_location_id'] = $request->input('rtd_location_id'); + } if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '1')) { $this->update_array['location_id'] = $request->input('rtd_location_id'); + $this->update_array['rtd_location_id'] = $request->input('rtd_location_id'); + } + if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '2')) { + $this->update_array['location_id'] = $request->input('rtd_location_id'); } } $changed = []; - $assetCollection = Asset::where('id' ,$assetId)->get(); + $asset = Asset::find($assetId); foreach ($this->update_array as $key => $value) { - if ($this->update_array[$key] != $assetCollection->toArray()[0][$key]) { - $changed[$key]['old'] = $assetCollection->toArray()[0][$key]; + if ($this->update_array[$key] != $asset->{$key}) { + $changed[$key]['old'] = $asset->{$key}; $changed[$key]['new'] = $this->update_array[$key]; } } - - $logAction = new Actionlog(); - $logAction->item_type = Asset::class; - $logAction->item_id = $assetId; - $logAction->created_at = date("Y-m-d H:i:s"); - $logAction->user_id = Auth::id(); - $logAction->log_meta = json_encode($changed); - $logAction->logaction('update'); - if($custom_fields_present) { - $asset = Asset::find($assetId); - $assetCustomFields = $asset->model()->first()->fieldset; - if($assetCustomFields && $assetCustomFields->fields) { - foreach ($assetCustomFields->fields as $field) { - if (array_key_exists($field->db_column, $this->update_array)) { - $asset->{$field->db_column} = $this->update_array[$field->db_column]; - $saved = $asset->save(); - if(!$saved) { - $error_bag[] = $asset->getErrors(); - } - continue; - } else { - $array = $this->update_array; - array_except($array, $field->db_column); - $asset->save($array); - } - if (!$asset->save()) { - $error_bag[] = $asset->getErrors(); - } - } - } - } else { - Asset::find($assetId)->update($this->update_array); - } - } - if(!empty($error_bag)) { - $errors = []; - //find the customfield name from the name of the messagebag items - foreach ($error_bag as $key => $bag) { - foreach($bag->keys() as $key => $value) { - CustomField::where('db_column', $value)->get()->map(function($item) use (&$errors) { - $errors[] = $item->name; - }); + if ($custom_fields_present) { + + $model = $asset->model()->first(); + + // Use the rules of the new model fieldsets if the model changed + if ($request->filled('model_id')) { + $this->update_array['model_id'] = $request->input('model_id'); + $model = \App\Models\AssetModel::find($request->input('model_id')); } - } - return redirect($bulk_back_url)->with('bulk_errors', array_unique($errors)); - } + + + // Make sure this model is valid + $assetCustomFields = ($model) ? $model->fieldset : null; + + if ($assetCustomFields && $assetCustomFields->fields) { + + foreach ($assetCustomFields->fields as $field) { + + if ((array_key_exists($field->db_column, $this->update_array)) && ($field->field_encrypted=='1')) { + $decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column}); + + /* + * Check if the decrypted existing value is different from one we just submitted + * and if not, pull it out of the object since it shouldn't really be updating at all. + * If we don't do this, it will try to re-encrypt it, and the same value encrypted two + * different times will have different values, so it will *look* like it was updated + * but it wasn't. + */ + if ($decrypted_old != $this->update_array[$field->db_column]) { + $asset->{$field->db_column} = \Crypt::encrypt($this->update_array[$field->db_column]); + } else { + /* + * Remove the encrypted custom field from the update_array, since nothing changed + */ + unset($this->update_array[$field->db_column]); + unset($asset->{$field->db_column}); + } + + /* + * These custom fields aren't encrypted, just carry on as usual + */ + } else { + + + if ((array_key_exists($field->db_column, $this->update_array)) && ($asset->{$field->db_column} != $this->update_array[$field->db_column])) { + + // Check if this is an array, and if so, flatten it + if (is_array($this->update_array[$field->db_column])) { + $asset->{$field->db_column} = implode(', ', $this->update_array[$field->db_column]); + } else { + $asset->{$field->db_column} = $this->update_array[$field->db_column]; + } + } + } + + } // endforeach + } // end custom field check + } // end custom fields handler + + + + // Check if it passes validation, and then try to save + if (!$asset->update($this->update_array)) { + + // Build the error array + foreach ($asset->getErrors()->toArray() as $key => $message) { + for ($x = 0; $x < count($message); $x++) { + $error_array[$key][] = trans('general.asset') . ' ' . $asset->id . ': ' . $message[$x]; + $has_errors++; + } + } + + } // end if saved + + } // end asset foreach + + if ($has_errors > 0) { + return redirect($bulk_back_url)->with('bulk_asset_errors', $error_array); + } + return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.update.success')); } // no values given, nothing to update diff --git a/app/Http/Controllers/Components/ComponentCheckinController.php b/app/Http/Controllers/Components/ComponentCheckinController.php index 49199aa6d..9f4724e35 100644 --- a/app/Http/Controllers/Components/ComponentCheckinController.php +++ b/app/Http/Controllers/Components/ComponentCheckinController.php @@ -96,8 +96,8 @@ class ComponentCheckinController extends Controller $asset = Asset::find($component_assets->asset_id); event(new CheckoutableCheckedIn($component, $asset, Auth::user(), $request->input('note'), Carbon::now())); - if($backto == 'asset'){ - return redirect()->route('hardware.view', $asset->id)->with('success', + if ($backto == 'asset'){ + return redirect()->route('hardware.show', $asset->id)->with('success', trans('admin/components/message.checkin.success')); } diff --git a/app/Http/Controllers/CustomFieldsController.php b/app/Http/Controllers/CustomFieldsController.php index c9579ae7e..ffe5eceec 100644 --- a/app/Http/Controllers/CustomFieldsController.php +++ b/app/Http/Controllers/CustomFieldsController.php @@ -110,6 +110,7 @@ class CustomFieldsController extends Controller "display_in_user_view" => $display_in_user_view, "auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0), "show_in_listview" => $request->get("show_in_listview", 0), + "show_in_requestable_list" => $request->get("show_in_requestable_list", 0), "user_id" => Auth::id() ]); @@ -267,6 +268,7 @@ class CustomFieldsController extends Controller $field->display_in_user_view = $display_in_user_view; $field->auto_add_to_fieldsets = $request->get("auto_add_to_fieldsets", 0); $field->show_in_listview = $request->get("show_in_listview", 0); + $field->show_in_requestable_list = $request->get("show_in_requestable_list", 0); if ($request->get('format') == 'CUSTOM REGEX') { $field->format = e($request->get('custom_format')); diff --git a/app/Http/Controllers/LabelsController.php b/app/Http/Controllers/LabelsController.php index ddff77196..950094bc4 100755 --- a/app/Http/Controllers/LabelsController.php +++ b/app/Http/Controllers/LabelsController.php @@ -7,8 +7,10 @@ use App\Models\AssetModel; use App\Models\Category; use App\Models\Company; use App\Models\Labels\Label; +use App\Models\Location; use App\Models\Manufacturer; use App\Models\Setting; +use App\Models\Supplier; use App\Models\User; use App\View\Label as LabelView; use Illuminate\Support\Facades\Storage; @@ -33,18 +35,20 @@ class LabelsController extends Controller $exampleAsset->name = 'JEN-867-5309'; $exampleAsset->asset_tag = '100001'; $exampleAsset->serial = 'SN9876543210'; + $exampleAsset->asset_eol_date = '2025-01-01'; + $exampleAsset->order_number = '12345'; + $exampleAsset->purchase_date = '2023-01-01'; + $exampleAsset->status_id = 1; - $exampleAsset->company = new Company(); - $exampleAsset->company->id = 999999; - $exampleAsset->company->name = 'Test Company Limited'; - $exampleAsset->company->image = 'company-image-test.png'; + $exampleAsset->company = new Company([ + 'name' => 'Test Company Limited', + 'phone' => '1-555-555-5555', + 'email' => 'company@example.com', + ]); - $exampleAsset->assignedto = new User(); - $exampleAsset->assignedto->id = 999999; - $exampleAsset->assignedto->first_name = 'Test'; - $exampleAsset->assignedto->last_name = 'Person'; - $exampleAsset->assignedto->username = 'Test.Person'; - $exampleAsset->assignedto->employee_num = '0123456789'; + $exampleAsset->setRelation('assignedTo', new User(['first_name' => 'Luke', 'last_name' => 'Skywalker'])); + $exampleAsset->defaultLoc = new Location(['name' => 'Building 1', 'phone' => '1-555-555-5555']); + $exampleAsset->location = new Location(['name' => 'Building 2', 'phone' => '1-555-555-5555']); $exampleAsset->model = new AssetModel(); $exampleAsset->model->id = 999999; @@ -53,6 +57,10 @@ class LabelsController extends Controller $exampleAsset->model->manufacturer = new Manufacturer(); $exampleAsset->model->manufacturer->id = 999999; $exampleAsset->model->manufacturer->name = 'Test Manufacturing Inc.'; + $exampleAsset->model->manufacturer->support_email = 'support@test.com'; + $exampleAsset->model->manufacturer->support_phone = '1-555-555-5555'; + $exampleAsset->model->manufacturer->support_url = 'https://example.com'; + $exampleAsset->supplier = new Supplier(['name' => 'Test Company Limited']); $exampleAsset->model->category = new Category(); $exampleAsset->model->category->id = 999999; $exampleAsset->model->category->name = 'Test Category'; diff --git a/app/Http/Controllers/Licenses/LicenseCheckinController.php b/app/Http/Controllers/Licenses/LicenseCheckinController.php index a78b54872..367ff3f1d 100644 --- a/app/Http/Controllers/Licenses/LicenseCheckinController.php +++ b/app/Http/Controllers/Licenses/LicenseCheckinController.php @@ -101,7 +101,7 @@ class LicenseCheckinController extends Controller // Was the asset updated? if ($licenseSeat->save()) { - event(new CheckoutableCheckedIn($licenseSeat, $return_to, Auth::user(), $request->input('note'))); + event(new CheckoutableCheckedIn($licenseSeat, $return_to, Auth::user(), $request->input('notes'))); if ($backTo == 'user') { return redirect()->route('users.show', $return_to->id)->with('success', trans('admin/licenses/message.checkin.success')); diff --git a/app/Http/Controllers/Licenses/LicenseCheckoutController.php b/app/Http/Controllers/Licenses/LicenseCheckoutController.php index ec6b4bbac..07ca8bbd5 100644 --- a/app/Http/Controllers/Licenses/LicenseCheckoutController.php +++ b/app/Http/Controllers/Licenses/LicenseCheckoutController.php @@ -105,7 +105,7 @@ class LicenseCheckoutController extends Controller $licenseSeat->assigned_to = $target->assigned_to; } if ($licenseSeat->save()) { - event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('note'))); + event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('notes'))); return true; } @@ -122,7 +122,7 @@ class LicenseCheckoutController extends Controller $licenseSeat->assigned_to = request('assigned_to'); if ($licenseSeat->save()) { - event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('note'))); + event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('notes'))); return true; } diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 279162421..04e3c434e 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -23,6 +23,7 @@ use Input; use League\Csv\Reader; use Symfony\Component\HttpFoundation\StreamedResponse; use League\Csv\EscapeFormula; +use App\Http\Requests\CustomAssetReportRequest; /** @@ -246,6 +247,9 @@ class ReportsController extends Controller trans('general.action'), trans('general.type'), trans('general.item'), + trans('general.license_serial'), + trans('general.model_name'), + trans('general.model_no'), 'To', trans('general.notes'), 'Changed', @@ -288,6 +292,9 @@ class ReportsController extends Controller $actionlog->present()->actionType(), e($actionlog->itemType()), ($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name, + ($actionlog->item->serial) ? $actionlog->item->serial : null, + ($actionlog->item->model) ? htmlspecialchars($actionlog->item->model->name, ENT_NOQUOTES) : null, + ($actionlog->item->model) ? $actionlog->item->model->model_number : null, $target_name, ($actionlog->note) ? e($actionlog->note) : '', $actionlog->log_meta, @@ -403,11 +410,12 @@ class ReportsController extends Controller * @since [v1.0] * @return \Illuminate\Http\Response */ - public function postCustom(Request $request) + public function postCustom(CustomAssetReportRequest $request) { ini_set('max_execution_time', env('REPORT_TIME_LIMIT', 12000)); //12000 seconds = 200 minutes $this->authorize('reports.view'); + \Debugbar::disable(); $customfields = CustomField::get(); $response = new StreamedResponse(function () use ($customfields, $request) { @@ -526,6 +534,30 @@ class ReportsController extends Controller $header[] = trans('admin/users/table.title'); } + if ($request->filled('phone')) { + $header[] = trans('admin/users/table.phone'); + } + + if ($request->filled('user_address')) { + $header[] = trans('admin/reports/general.custom_export.user_address'); + } + + if ($request->filled('user_city')) { + $header[] = trans('admin/reports/general.custom_export.user_city'); + } + + if ($request->filled('user_state')) { + $header[] = trans('admin/reports/general.custom_export.user_state'); + } + + if ($request->filled('user_country')) { + $header[] = trans('admin/reports/general.custom_export.user_country'); + } + + if ($request->filled('user_zip')) { + $header[] = trans('admin/reports/general.custom_export.user_zip'); + } + if ($request->filled('status')) { $header[] = trans('general.status'); } @@ -645,7 +677,7 @@ class ReportsController extends Controller if (($request->filled('created_start')) && ($request->filled('created_end'))) { $created_start = \Carbon::parse($request->input('created_start'))->startOfDay(); $created_end = \Carbon::parse($request->input('created_end'))->endOfDay(); - + $assets->whereBetween('assets.created_at', [$created_start, $created_end]); } if (($request->filled('checkout_date_start')) && ($request->filled('checkout_date_end'))) { @@ -656,22 +688,22 @@ class ReportsController extends Controller } if (($request->filled('checkin_date_start'))) { - $assets->whereBetween('last_checkin', [ - Carbon::parse($request->input('checkin_date_start'))->startOfDay(), - // use today's date is `checkin_date_end` is not provided - Carbon::parse($request->input('checkin_date_end', now()))->endOfDay(), - ]); + $assets->whereBetween('last_checkin', [ + Carbon::parse($request->input('checkin_date_start'))->startOfDay(), + // use today's date is `checkin_date_end` is not provided + Carbon::parse($request->input('checkin_date_end', now()))->endOfDay(), + ]); } if (($request->filled('expected_checkin_start')) && ($request->filled('expected_checkin_end'))) { - $assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]); + $assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]); } if (($request->filled('last_audit_start')) && ($request->filled('last_audit_end'))) { - $last_audit_start = \Carbon::parse($request->input('last_audit_start'))->startOfDay(); - $last_audit_end = \Carbon::parse($request->input('last_audit_end'))->endOfDay(); + $last_audit_start = \Carbon::parse($request->input('last_audit_start'))->startOfDay(); + $last_audit_end = \Carbon::parse($request->input('last_audit_end'))->endOfDay(); - $assets->whereBetween('assets.last_audit_date', [$last_audit_start, $last_audit_end]); + $assets->whereBetween('assets.last_audit_date', [$last_audit_start, $last_audit_end]); } if (($request->filled('next_audit_start')) && ($request->filled('next_audit_end'))) { @@ -742,7 +774,7 @@ class ReportsController extends Controller } if ($request->filled('eol')) { - $row[] = ($asset->purchase_date != '') ? $asset->present()->eol_date() : ''; + $row[] = ($asset->asset_eol_date) ? $asset->asset_eol_date : ''; } if ($request->filled('order')) { @@ -826,6 +858,54 @@ class ReportsController extends Controller } } + if ($request->filled('phone')) { + if ($asset->checkedOutToUser()) { + $row[] = ($asset->assignedto) ? $asset->assignedto->phone : ''; + } else { + $row[] = ''; // Empty string if unassigned + } + } + + if ($request->filled('user_address')) { + if ($asset->checkedOutToUser()) { + $row[] = ($asset->assignedto) ? $asset->assignedto->address : ''; + } else { + $row[] = ''; // Empty string if unassigned + } + } + + if ($request->filled('user_city')) { + if ($asset->checkedOutToUser()) { + $row[] = ($asset->assignedto) ? $asset->assignedto->city : ''; + } else { + $row[] = ''; // Empty string if unassigned + } + } + + if ($request->filled('user_state')) { + if ($asset->checkedOutToUser()) { + $row[] = ($asset->assignedto) ? $asset->assignedto->state : ''; + } else { + $row[] = ''; // Empty string if unassigned + } + } + + if ($request->filled('user_country')) { + if ($asset->checkedOutToUser()) { + $row[] = ($asset->assignedto) ? $asset->assignedto->country : ''; + } else { + $row[] = ''; // Empty string if unassigned + } + } + + if ($request->filled('user_zip')) { + if ($asset->checkedOutToUser()) { + $row[] = ($asset->assignedto) ? $asset->assignedto->zip : ''; + } else { + $row[] = ''; // Empty string if unassigned + } + } + if ($request->filled('status')) { $row[] = ($asset->assetstatus) ? $asset->assetstatus->name.' ('.$asset->present()->statusMeta.')' : ''; } diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index 7a7aa45b6..989fe8c49 100755 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -7,6 +7,7 @@ use App\Helpers\StorageHelper; use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\SettingsSamlRequest; use App\Http\Requests\SetupUserRequest; +use App\Models\CustomField; use App\Models\Group; use App\Models\Setting; use App\Models\Asset; @@ -26,7 +27,7 @@ use Response; use App\Http\Requests\SlackSettingsRequest; use Illuminate\Support\Str; use Illuminate\Support\Facades\Artisan; -use Validator; +use Illuminate\Support\Facades\Validator; /** * This controller handles all actions related to Settings for @@ -809,9 +810,10 @@ class SettingsController extends Controller */ public function getLabels() { - $setting = Setting::getSettings(); - - return view('settings.labels', compact('setting')); + return view('settings.labels', [ + 'setting' => Setting::getSettings(), + 'customFields' => CustomField::all(), + ]); } /** @@ -1248,13 +1250,11 @@ class SettingsController extends Controller if (!$request->hasFile('file')) { return redirect()->route('settings.backups.index')->with('error', 'No file uploaded'); } else { + $max_file_size = Helper::file_upload_max_size(); - - $rules = [ + $validator = Validator::make($request->all(), [ 'file' => 'required|mimes:zip|max:'.$max_file_size, - ]; - - $validator = \Validator::make($request->all(), $rules); + ]); if ($validator->passes()) { @@ -1265,7 +1265,7 @@ class SettingsController extends Controller return redirect()->route('settings.backups.index')->with('success', 'File uploaded'); } - return redirect()->route('settings.backups.index')->withErrors($request->getErrors()); + return redirect()->route('settings.backups.index')->withErrors($validator); } diff --git a/app/Http/Controllers/Users/BulkUsersController.php b/app/Http/Controllers/Users/BulkUsersController.php index a2d3d496d..ca1e2a489 100644 --- a/app/Http/Controllers/Users/BulkUsersController.php +++ b/app/Http/Controllers/Users/BulkUsersController.php @@ -125,10 +125,26 @@ class BulkUsersController extends Controller ]; } + /** + * Check to see if the user wants to actually blank out the values vs skip them + */ if ($request->input('null_location_id')=='1') { $this->update_array['location_id'] = null; } + if ($request->input('null_department_id')=='1') { + $this->update_array['department_id'] = null; + } + + if ($request->input('null_manager_id')=='1') { + $this->update_array['manager_id'] = null; + } + + if ($request->input('null_company_id')=='1') { + $this->update_array['company_id'] = null; + } + + if (! $manager_conflict) { $this->conditionallyAddItem('manager_id'); } diff --git a/app/Http/Controllers/Users/LDAPImportController.php b/app/Http/Controllers/Users/LDAPImportController.php index 88a6b207d..e535a171a 100644 --- a/app/Http/Controllers/Users/LDAPImportController.php +++ b/app/Http/Controllers/Users/LDAPImportController.php @@ -49,15 +49,19 @@ class LDAPImportController extends Controller { $this->authorize('update', User::class); // Call Artisan LDAP import command. - $location_id = $request->input('location_id'); - Artisan::call('snipeit:ldap-sync', ['--location_id' => $location_id, '--json_summary' => true]); + + Artisan::call('snipeit:ldap-sync', ['--location_id' => $request->input('location_id'), '--json_summary' => true]); // Collect and parse JSON summary. $ldap_results_json = Artisan::output(); $ldap_results = json_decode($ldap_results_json, true); + if (!$ldap_results) { + return redirect()->back()->withInput()->with('error', trans('general.no_results')); + } // Direct user to appropriate status page. if ($ldap_results['error']) { + return redirect()->back()->withInput()->with('error', $ldap_results['error_message']); } diff --git a/app/Http/Requests/CustomAssetReportRequest.php b/app/Http/Requests/CustomAssetReportRequest.php new file mode 100644 index 000000000..2a8fbe7fa --- /dev/null +++ b/app/Http/Requests/CustomAssetReportRequest.php @@ -0,0 +1,46 @@ + 'date|date_format:Y-m-d|nullable', + 'purchase_end' => 'date|date_format:Y-m-d|nullable', + 'created_start' => 'date|date_format:Y-m-d|nullable', + 'created_end' => 'date|date_format:Y-m-d|nullable', + 'checkout_date_start' => 'date|date_format:Y-m-d|nullable', + 'checkout_date_end' => 'date|date_format:Y-m-d|nullable', + 'expected_checkin_start' => 'date|date_format:Y-m-d|nullable', + 'expected_checkin_end' => 'date|date_format:Y-m-d|nullable', + 'checkin_date_start' => 'date|date_format:Y-m-d|nullable', + 'checkin_date_end' => 'date|date_format:Y-m-d|nullable', + 'last_audit_start' => 'date|date_format:Y-m-d|nullable', + 'last_audit_end' => 'date|date_format:Y-m-d|nullable', + 'next_audit_start' => 'date|date_format:Y-m-d|nullable', + 'next_audit_end' => 'date|date_format:Y-m-d|nullable', + ]; + } + + public function response(array $errors) + { + return $this->redirector->back()->withInput()->withErrors($errors, $this->errorBag); + } +} diff --git a/app/Http/Requests/StoreAssetRequest.php b/app/Http/Requests/StoreAssetRequest.php new file mode 100644 index 000000000..254895f13 --- /dev/null +++ b/app/Http/Requests/StoreAssetRequest.php @@ -0,0 +1,40 @@ +getRules(), + parent::rules(), + ); + + return $rules; + } +} diff --git a/app/Http/Transformers/ActionlogsTransformer.php b/app/Http/Transformers/ActionlogsTransformer.php index d3af3bb75..1de914332 100644 --- a/app/Http/Transformers/ActionlogsTransformer.php +++ b/app/Http/Transformers/ActionlogsTransformer.php @@ -3,6 +3,7 @@ namespace App\Http\Transformers; use App\Helpers\Helper; use App\Models\Actionlog; +use App\Models\Asset; use App\Models\CustomField; use App\Models\Setting; use App\Models\Company; @@ -12,6 +13,7 @@ use App\Models\AssetModel; use Illuminate\Database\Eloquent\Collection; use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Support\Facades\Crypt; +use Illuminate\Support\Facades\Gate; class ActionlogsTransformer { @@ -98,6 +100,13 @@ class ActionlogsTransformer \Log::debug('custom fields do not match'); $clean_meta[$fieldname]['old'] = "************"; $clean_meta[$fieldname]['new'] = "************"; + + // Display the changes if the user is an admin or superadmin + if (Gate::allows('admin')) { + $clean_meta[$fieldname]['old'] = ($enc_old) ? unserialize($enc_old): ''; + $clean_meta[$fieldname]['new'] = ($enc_new) ? unserialize($enc_new): ''; + } + } diff --git a/app/Http/Transformers/AssetsTransformer.php b/app/Http/Transformers/AssetsTransformer.php index 68dc731f0..d38eac241 100644 --- a/app/Http/Transformers/AssetsTransformer.php +++ b/app/Http/Transformers/AssetsTransformer.php @@ -7,7 +7,8 @@ use App\Models\Asset; use App\Models\Setting; use Illuminate\Support\Facades\Gate; use Illuminate\Database\Eloquent\Collection; - +use Carbon\Carbon; +use Auth; class AssetsTransformer { @@ -38,7 +39,7 @@ class AssetsTransformer 'byod' => ($asset->byod ? true : false), 'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null, - 'eol' => (($asset->model) && ($asset->model->eol != '')) ? $asset->model->eol : null, + 'eol' => (($asset->asset_eol_date != '') && ($asset->purchase_date != '')) ? Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date).' months' : null, 'asset_eol_date' => ($asset->asset_eol_date != '') ? Helper::getFormattedDateObject($asset->asset_eol_date, 'date') : null, 'status_label' => ($asset->assetstatus) ? [ 'id' => (int) $asset->assetstatus->id, @@ -231,6 +232,29 @@ class AssetsTransformer 'assigned_to_self' => ($asset->assigned_to == \Auth::user()->id), ]; + if (($asset->model) && ($asset->model->fieldset) && ($asset->model->fieldset->fields->count() > 0)) { + $fields_array = []; + + foreach ($asset->model->fieldset->fields as $field) { + + // Only display this if it's allowed via the custom field setting + if (($field->field_encrypted=='0') && ($field->show_in_requestable_list=='1')) { + + $value = $asset->{$field->db_column}; + if (($field->format == 'DATE') && (!is_null($value)) && ($value != '')) { + $value = Helper::getFormattedDateObject($value, 'date', false); + } + + $fields_array[$field->db_column] = e($value); + } + + $array['custom_fields'] = $fields_array; + } + } else { + $array['custom_fields'] = new \stdClass; // HACK to force generation of empty object instead of empty list + } + + $permissions_array['available_actions'] = [ 'cancel' => ($asset->isRequestedBy(\Auth::user())) ? true : false, 'request' => ($asset->isRequestedBy(\Auth::user())) ? false : true, diff --git a/app/Importer/AccessoryImporter.php b/app/Importer/AccessoryImporter.php index 417075ef3..9901fb70d 100644 --- a/app/Importer/AccessoryImporter.php +++ b/app/Importer/AccessoryImporter.php @@ -34,7 +34,7 @@ class AccessoryImporter extends ItemImporter } $this->log('Updating Accessory'); - $this->item['model_number'] = $this->findCsvMatch($row, "model_number"); + $this->item['model_number'] = trim($this->findCsvMatch($row, "model_number")); $accessory->update($this->sanitizeItemForUpdating($accessory)); $accessory->save(); diff --git a/app/Importer/AssetImporter.php b/app/Importer/AssetImporter.php index 76eae0739..cf762a8fd 100644 --- a/app/Importer/AssetImporter.php +++ b/app/Importer/AssetImporter.php @@ -5,6 +5,9 @@ namespace App\Importer; use App\Models\Asset; use App\Models\AssetModel; use App\Models\Statuslabel; +use App\Models\User; +use App\Events\CheckoutableCheckedIn; +use Illuminate\Support\Facades\Auth; use Carbon\Carbon; class AssetImporter extends ItemImporter @@ -80,13 +83,13 @@ class AssetImporter extends ItemImporter $this->log('No Matching Asset, Creating a new one'); $asset = new Asset; } - $this->item['notes'] = $this->findCsvMatch($row, 'asset_notes'); - $this->item['image'] = $this->findCsvMatch($row, 'image'); - $this->item['requestable'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable')) == 1) ? '1' : 0; + $this->item['notes'] = trim($this->findCsvMatch($row, 'asset_notes')); + $this->item['image'] = trim($this->findCsvMatch($row, 'image')); + $this->item['requestable'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'))) == 1) ? '1' : 0; $asset->requestable = $this->item['requestable']; - $this->item['warranty_months'] = intval($this->findCsvMatch($row, 'warranty_months')); + $this->item['warranty_months'] = intval(trim($this->findCsvMatch($row, 'warranty_months'))); $this->item['model_id'] = $this->createOrFetchAssetModel($row); - $this->item['byod'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'byod')) == 1) ? '1' : 0; + $this->item['byod'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'byod'))) == 1) ? '1' : 0; // If no status ID is found @@ -141,7 +144,13 @@ class AssetImporter extends ItemImporter // If we have a target to checkout to, lets do so. //-- user_id is a property of the abstract class Importer, which this class inherits from and it's setted by //-- the class that needs to use it (command importer or GUI importer inside the project). - if (isset($target)) { + if (isset($target) && ($target !== false)) { + if (!is_null($asset->assigned_to)){ + if ($asset->assigned_to != $target->id){ + event(new CheckoutableCheckedIn($asset, User::find($asset->assigned_to), Auth::user(), $asset->notes, date('Y-m-d H:i:s'))); + } + } + $asset->fresh()->checkOut($target, $this->user_id, date('Y-m-d H:i:s'), null, $asset->notes, $asset->name); } diff --git a/app/Importer/ComponentImporter.php b/app/Importer/ComponentImporter.php index de3ee14d1..71ded1b0e 100644 --- a/app/Importer/ComponentImporter.php +++ b/app/Importer/ComponentImporter.php @@ -28,8 +28,8 @@ class ComponentImporter extends ItemImporter { $component = null; $this->log('Creating Component'); - $component = Component::where('name', $this->item['name']) - ->where('serial', $this->item['serial']) + $component = Component::where('name', trim($this->item['name'])) + ->where('serial', trim($this->item['serial'])) ->first(); if ($component) { diff --git a/app/Importer/ConsumableImporter.php b/app/Importer/ConsumableImporter.php index b0dea1f51..5a65514de 100644 --- a/app/Importer/ConsumableImporter.php +++ b/app/Importer/ConsumableImporter.php @@ -26,7 +26,7 @@ class ConsumableImporter extends ItemImporter */ public function createConsumableIfNotExists($row) { - $consumable = Consumable::where('name', $this->item['name'])->first(); + $consumable = Consumable::where('name', trim($this->item['name']))->first(); if ($consumable) { if (! $this->updating) { $this->log('A matching Consumable '.$this->item['name'].' already exists. '); @@ -41,9 +41,9 @@ class ConsumableImporter extends ItemImporter } $this->log('No matching consumable, creating one'); $consumable = new Consumable(); - $this->item['model_number'] = $this->findCsvMatch($row, 'model_number'); - $this->item['item_no'] = $this->findCsvMatch($row, 'item_number'); - $this->item['min_amt'] = $this->findCsvMatch($row, "min_amt"); + $this->item['model_number'] = trim($this->findCsvMatch($row, 'model_number')); + $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(); diff --git a/app/Importer/Importer.php b/app/Importer/Importer.php index 588a02d67..961f4af52 100644 --- a/app/Importer/Importer.php +++ b/app/Importer/Importer.php @@ -19,22 +19,76 @@ abstract class Importer * Id of User performing import * @var */ + protected $user_id; /** * Are we updating items in the import * @var bool */ + protected $updating; + /** * Default Map of item fields->csv names * * This has been moved into app/Http/Livewire/Importer.php to be more granular. - * @todo - remove references to this property since we don't use it anymore. + * This private variable is ONLY used for the cli-importer. * + * @todo - find a way to make this less duplicative * @var array */ private $defaultFieldMap = [ - + 'asset_tag' => 'asset tag', + 'activated' => 'activated', + 'category' => 'category', + 'checkout_class' => 'checkout type', // Supports Location or User for assets. Using checkout_class instead of checkout_type because type exists on asset already. + 'checkout_location' => 'checkout location', + 'company' => 'company', + 'item_name' => 'item name', + 'item_number' => 'item number', + 'image' => 'image', + 'expiration_date' => 'expiration date', + 'location' => 'location', + 'notes' => 'notes', + 'license_email' => 'licensed to email', + 'license_name' => 'licensed to name', + 'maintained' => 'maintained', + 'manufacturer' => 'manufacturer', + 'asset_model' => 'model name', + 'model_number' => 'model number', + 'order_number' => 'order number', + 'purchase_cost' => 'purchase cost', + 'purchase_date' => 'purchase date', + 'purchase_order' => 'purchase order', + 'qty' => 'quantity', + 'reassignable' => 'reassignable', + 'requestable' => 'requestable', + 'seats' => 'seats', + 'serial' => 'serial number', + 'status' => 'status', + 'supplier' => 'supplier', + 'termination_date' => 'termination date', + 'warranty_months' => 'warranty', + 'full_name' => 'full name', + 'email' => 'email', + 'username' => 'username', + 'address' => 'address', + 'address2' => 'address2', + 'city' => 'city', + 'state' => 'state', + 'country' => 'country', + 'zip' => 'zip', + 'jobtitle' => 'job title', + 'employee_num' => 'employee number', + 'phone_number' => 'phone number', + 'first_name' => 'first name', + 'last_name' => 'last name', + 'department' => 'department', + 'manager_name' => 'manager full name', + 'manager_username' => 'manager username', + 'min_amt' => 'minimum quantity', + 'remote' => 'remote', + 'vip' => 'vip', ]; /** * Map of item fields->csv names @@ -281,9 +335,11 @@ abstract class Importer $user_array['email'] = User::generateEmailFromFullName($user_array['full_name']); } + // Get some variables for $user_formatted_array in case we need them later + $user_formatted_array = User::generateFormattedNameFromFullName($user_array['full_name'], Setting::getSettings()->username_format); + if (empty($user_array['first_name'])) { // Get some fields for first name and last name based off of full name - $user_formatted_array = User::generateFormattedNameFromFullName($user_array['full_name'], Setting::getSettings()->username_format); $user_array['first_name'] = $user_formatted_array['first_name']; $user_array['last_name'] = $user_formatted_array['last_name']; } diff --git a/app/Importer/ItemImporter.php b/app/Importer/ItemImporter.php index 4e53ef60b..e1b0f1c28 100644 --- a/app/Importer/ItemImporter.php +++ b/app/Importer/ItemImporter.php @@ -372,7 +372,7 @@ class ItemImporter extends Importer if (empty($asset_statuslabel_name)) { return null; } - $status = Statuslabel::where(['name' => $asset_statuslabel_name])->first(); + $status = Statuslabel::where(['name' => trim($asset_statuslabel_name)])->first(); if ($status) { $this->log('A matching Status '.$asset_statuslabel_name.' already exists'); @@ -381,7 +381,7 @@ class ItemImporter extends Importer } $this->log('Creating a new status'); $status = new Statuslabel(); - $status->name = $asset_statuslabel_name; + $status->name = trim($asset_statuslabel_name); $status->deployable = 1; $status->pending = 0; @@ -420,7 +420,7 @@ class ItemImporter extends Importer //Otherwise create a manufacturer. $manufacturer = new Manufacturer(); - $manufacturer->name = $item_manufacturer; + $manufacturer->name = trim($item_manufacturer); $manufacturer->user_id = $this->user_id; if ($manufacturer->save()) { diff --git a/app/Importer/LicenseImporter.php b/app/Importer/LicenseImporter.php index 6c43734b9..393d00367 100644 --- a/app/Importer/LicenseImporter.php +++ b/app/Importer/LicenseImporter.php @@ -55,19 +55,19 @@ class LicenseImporter extends ItemImporter $this->log('No Matching License, Creating a new one'); $license = new License; } - $asset_tag = $this->item['asset_tag'] = $this->findCsvMatch($row, 'asset_tag'); // used for checkout out to an asset. + $asset_tag = $this->item['asset_tag'] = trim($this->findCsvMatch($row, 'asset_tag')); // used for checkout out to an asset. $this->item["expiration_date"] = null; if ($this->findCsvMatch($row, "expiration_date")!='') { - $this->item["expiration_date"] = date("Y-m-d 00:00:01", strtotime($this->findCsvMatch($row, "expiration_date"))); + $this->item["expiration_date"] = date("Y-m-d 00:00:01", strtotime(trim($this->findCsvMatch($row, "expiration_date")))); } - $this->item['license_email'] = $this->findCsvMatch($row, 'license_email'); - $this->item['license_name'] = $this->findCsvMatch($row, 'license_name'); - $this->item['maintained'] = $this->findCsvMatch($row, 'maintained'); - $this->item['purchase_order'] = $this->findCsvMatch($row, 'purchase_order'); - $this->item['order_number'] = $this->findCsvMatch($row, 'order_number'); - $this->item['reassignable'] = $this->findCsvMatch($row, 'reassignable'); - $this->item['manufacturer'] = $this->createOrFetchManufacturer($this->findCsvMatch($row, 'manufacturer')); + $this->item['license_email'] = trim($this->findCsvMatch($row, 'license_email')); + $this->item['license_name'] = trim($this->findCsvMatch($row, 'license_name')); + $this->item['maintained'] = trim($this->findCsvMatch($row, 'maintained')); + $this->item['purchase_order'] = trim($this->findCsvMatch($row, 'purchase_order')); + $this->item['order_number'] = trim($this->findCsvMatch($row, 'order_number')); + $this->item['reassignable'] = trim($this->findCsvMatch($row, 'reassignable')); + $this->item['manufacturer'] = $this->createOrFetchManufacturer(trim($this->findCsvMatch($row, 'manufacturer'))); if($this->item['reassignable'] == "") { diff --git a/app/Importer/LocationImporter.php b/app/Importer/LocationImporter.php index 25140abe0..47a157aa7 100644 --- a/app/Importer/LocationImporter.php +++ b/app/Importer/LocationImporter.php @@ -53,21 +53,21 @@ class LocationImporter extends ItemImporter } // Pull the records from the CSV to determine their values - $this->item['name'] = $this->findCsvMatch($row, 'name'); - $this->item['address'] = $this->findCsvMatch($row, 'address'); - $this->item['address2'] = $this->findCsvMatch($row, 'address2'); - $this->item['city'] = $this->findCsvMatch($row, 'city'); - $this->item['state'] = $this->findCsvMatch($row, 'state'); - $this->item['country'] = $this->findCsvMatch($row, 'country'); - $this->item['zip'] = $this->findCsvMatch($row, 'zip'); - $this->item['currency'] = $this->findCsvMatch($row, 'currency'); - $this->item['ldap_ou'] = $this->findCsvMatch($row, 'ldap_ou'); - $this->item['manager'] = $this->findCsvMatch($row, 'manager'); - $this->item['manager_username'] = $this->findCsvMatch($row, 'manager_username'); + $this->item['name'] = trim($this->findCsvMatch($row, 'name')); + $this->item['address'] = trim($this->findCsvMatch($row, 'address')); + $this->item['address2'] = trim($this->findCsvMatch($row, 'address2')); + $this->item['city'] = trim($this->findCsvMatch($row, 'city')); + $this->item['state'] = trim($this->findCsvMatch($row, 'state')); + $this->item['country'] = trim($this->findCsvMatch($row, 'country')); + $this->item['zip'] = trim($this->findCsvMatch($row, 'zip')); + $this->item['currency'] = trim($this->findCsvMatch($row, 'currency')); + $this->item['ldap_ou'] = trim($this->findCsvMatch($row, 'ldap_ou')); + $this->item['manager'] = trim($this->findCsvMatch($row, 'manager')); + $this->item['manager_username'] = trim($this->findCsvMatch($row, 'manager_username')); $this->item['user_id'] = \Auth::user()->id; if ($this->findCsvMatch($row, 'parent_location')) { - $this->item['parent_id'] = $this->createOrFetchLocation($this->findCsvMatch($row, 'parent_location')); + $this->item['parent_id'] = $this->createOrFetchLocation(trim($this->findCsvMatch($row, 'parent_location'))); } if (!empty($this->item['manager'])) { diff --git a/app/Importer/UserImporter.php b/app/Importer/UserImporter.php index e13d4c4cc..040f34f9e 100644 --- a/app/Importer/UserImporter.php +++ b/app/Importer/UserImporter.php @@ -42,32 +42,32 @@ class UserImporter extends ItemImporter public function createUserIfNotExists(array $row) { // Pull the records from the CSV to determine their values - $this->item['id'] = $this->findCsvMatch($row, 'id'); - $this->item['username'] = $this->findCsvMatch($row, 'username'); - $this->item['first_name'] = $this->findCsvMatch($row, 'first_name'); - $this->item['last_name'] = $this->findCsvMatch($row, 'last_name'); - $this->item['email'] = $this->findCsvMatch($row, 'email'); - $this->item['gravatar'] = $this->findCsvMatch($row, 'gravatar'); - $this->item['phone'] = $this->findCsvMatch($row, 'phone_number'); - $this->item['website'] = $this->findCsvMatch($row, 'website'); - $this->item['jobtitle'] = $this->findCsvMatch($row, 'jobtitle'); - $this->item['address'] = $this->findCsvMatch($row, 'address'); - $this->item['city'] = $this->findCsvMatch($row, 'city'); - $this->item['state'] = $this->findCsvMatch($row, 'state'); - $this->item['country'] = $this->findCsvMatch($row, 'country'); - $this->item['start_date'] = $this->findCsvMatch($row, 'start_date'); - $this->item['end_date'] = $this->findCsvMatch($row, 'end_date'); - $this->item['zip'] = $this->findCsvMatch($row, 'zip'); - $this->item['activated'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'activated')) == 1) ? '1' : 0; - $this->item['employee_num'] = $this->findCsvMatch($row, 'employee_num'); - $this->item['department_id'] = $this->createOrFetchDepartment($this->findCsvMatch($row, 'department')); + $this->item['id'] = trim($this->findCsvMatch($row, 'id')); + $this->item['username'] = trim($this->findCsvMatch($row, 'username')); + $this->item['first_name'] = trim($this->findCsvMatch($row, 'first_name')); + $this->item['last_name'] = trim($this->findCsvMatch($row, 'last_name')); + $this->item['email'] = trim($this->findCsvMatch($row, 'email')); + $this->item['gravatar'] = trim($this->findCsvMatch($row, 'gravatar')); + $this->item['phone'] = trim($this->findCsvMatch($row, 'phone_number')); + $this->item['website'] = trim($this->findCsvMatch($row, 'website')); + $this->item['jobtitle'] = trim($this->findCsvMatch($row, 'jobtitle')); + $this->item['address'] = trim($this->findCsvMatch($row, 'address')); + $this->item['city'] = trim($this->findCsvMatch($row, 'city')); + $this->item['state'] = trim($this->findCsvMatch($row, 'state')); + $this->item['country'] = trim($this->findCsvMatch($row, 'country')); + $this->item['start_date'] = trim($this->findCsvMatch($row, 'start_date')); + $this->item['end_date'] = trim($this->findCsvMatch($row, 'end_date')); + $this->item['zip'] = trim($this->findCsvMatch($row, 'zip')); + $this->item['activated'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'activated'))) == 1) ? '1' : 0; + $this->item['employee_num'] = trim($this->findCsvMatch($row, 'employee_num')); + $this->item['department_id'] = trim($this->createOrFetchDepartment(trim($this->findCsvMatch($row, 'department')))); $this->item['manager_id'] = $this->fetchManager($this->findCsvMatch($row, 'manager_first_name'), $this->findCsvMatch($row, 'manager_last_name')); - $this->item['remote'] =($this->fetchHumanBoolean($this->findCsvMatch($row, 'remote')) ==1 ) ? '1' : 0; - $this->item['vip'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'vip')) ==1 ) ? '1' : 0; - $this->item['autoassign_licenses'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'autoassign_licenses')) ==1 ) ? '1' : 0; + $this->item['remote'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'remote'))) == 1 ) ? '1' : 0; + $this->item['vip'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'vip'))) ==1 ) ? '1' : 0; + $this->item['autoassign_licenses'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'autoassign_licenses'))) ==1 ) ? '1' : 0; - $user_department = $this->findCsvMatch($row, 'department'); + $user_department = trim($this->findCsvMatch($row, 'department')); if ($this->shouldUpdateField($user_department)) { $this->item['department_id'] = $this->createOrFetchDepartment($user_department); } diff --git a/app/Models/Asset.php b/app/Models/Asset.php index 1ce38bca0..a7d4d7442 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -91,7 +91,7 @@ class Asset extends Depreciable protected $rules = [ 'name' => 'max:255|nullable', - 'model_id' => 'required|integer|exists:models,id,deleted_at,NULL', + 'model_id' => 'required|integer|exists:models,id,deleted_at,NULL|not_array', 'status_id' => 'required|integer|exists:status_labels,id', 'company_id' => 'integer|nullable', 'warranty_months' => 'numeric|nullable|digits_between:0,240', @@ -100,7 +100,7 @@ class Asset extends Depreciable 'expected_checkin' => 'date|nullable', 'location_id' => 'exists:locations,id|nullable', 'rtd_location_id' => 'exists:locations,id|nullable', - 'asset_tag' => 'required|min:1|max:255|unique_undeleted', + 'asset_tag' => 'required|min:1|max:255|unique_undeleted:assets,asset_tag', 'purchase_date' => 'date|date_format:Y-m-d|nullable', 'serial' => 'unique_serial|nullable', 'purchase_cost' => 'numeric|nullable|gte:0', @@ -212,16 +212,16 @@ class Asset extends Depreciable $this->rules += $model->fieldset->validation_rules(); - foreach ($this->model->fieldset->fields as $field){ - if($field->format == 'BOOLEAN'){ - $this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN); + if ($this->model->fieldset){ + foreach ($this->model->fieldset->fields as $field){ + if($field->format == 'BOOLEAN'){ + $this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN); + } } } } } - - return parent::save($params); } @@ -789,7 +789,6 @@ class Asset extends Depreciable } - /** * Get the next autoincremented asset tag * @@ -952,6 +951,7 @@ class Asset extends Depreciable ->orWhere('assets_users.first_name', 'LIKE', '%'.$term.'%') ->orWhere('assets_users.last_name', 'LIKE', '%'.$term.'%') ->orWhere('assets_users.username', 'LIKE', '%'.$term.'%') + ->orWhere('assets_users.employee_num', 'LIKE', '%'.$term.'%') ->orWhereMultipleColumns([ 'assets_users.first_name', 'assets_users.last_name', diff --git a/app/Models/CustomField.php b/app/Models/CustomField.php index c98dbe637..c1826a94d 100644 --- a/app/Models/CustomField.php +++ b/app/Models/CustomField.php @@ -53,6 +53,12 @@ class CustomField extends Model 'field_encrypted' => 'nullable|boolean', 'auto_add_to_fieldsets' => 'boolean', 'show_in_listview' => 'boolean', + 'show_in_requestable_list' => 'boolean', + 'show_in_email' => 'boolean', + ]; + + protected $casts = [ + 'show_in_requestable_list' => 'boolean', ]; /** @@ -72,7 +78,8 @@ class CustomField extends Model 'display_in_user_view', 'auto_add_to_fieldsets', 'show_in_listview', - + 'show_in_email', + 'show_in_requestable_list', ]; /** @@ -243,8 +250,6 @@ class CustomField extends Model /** * Gets the DB column name. * - * @todo figure out if this is still needed? I don't know WTF it's for. - * * @author [A. Gianotto] [] * @since [v3.0] * @return string diff --git a/app/Models/CustomFieldset.php b/app/Models/CustomFieldset.php index a2698d818..a62f96d63 100644 --- a/app/Models/CustomFieldset.php +++ b/app/Models/CustomFieldset.php @@ -92,6 +92,8 @@ class CustomFieldset extends Model array_push($rule, $field->attributes['format']); $rules[$field->db_column_name()] = $rule; + //add not_array to rules for all fields + $rules[$field->db_column_name()][] = 'not_array'; } return $rules; diff --git a/app/Models/Labels/FieldOption.php b/app/Models/Labels/FieldOption.php index 76427acca..7e45cc0ce 100644 --- a/app/Models/Labels/FieldOption.php +++ b/app/Models/Labels/FieldOption.php @@ -14,6 +14,14 @@ class FieldOption { public function getValue(Asset $asset) { $dataPath = collect(explode('.', $this->dataSource)); + + // assignedTo directly on the asset is a special case where + // we want to avoid returning the property directly + // and instead return the entity's presented name. + if ($dataPath[0] === 'assignedTo'){ + return $asset->assignedTo ? $asset->assignedTo->present()->fullName() : null; + } + return $dataPath->reduce(function ($myValue, $path) { try { return $myValue ? $myValue->{$path} : ${$myValue}; } catch (\Exception $e) { return $myValue; } @@ -46,4 +54,4 @@ class FieldOption { return $option; } } -} \ No newline at end of file +} diff --git a/app/Models/License.php b/app/Models/License.php index 44f1f45b7..8e6ed79f1 100755 --- a/app/Models/License.php +++ b/app/Models/License.php @@ -32,6 +32,7 @@ class License extends Depreciable protected $guarded = 'id'; protected $table = 'licenses'; + protected $casts = [ 'purchase_date' => 'date', 'expiration_date' => 'date', @@ -668,7 +669,7 @@ class License extends Depreciable return self::whereNotNull('expiration_date') ->whereNull('deleted_at') - ->whereRaw(DB::raw('DATE_SUB(`expiration_date`,INTERVAL '.$days.' DAY) <= DATE(NOW()) ')) + ->whereRaw('DATE_SUB(`expiration_date`,INTERVAL '.$days.' DAY) <= DATE(NOW()) ') ->where('expiration_date', '>', date('Y-m-d')) ->orderBy('expiration_date', 'ASC') ->get(); diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 6c95d6b01..caf142cbd 100755 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -92,6 +92,10 @@ class Setting extends Model 'google_client_secret', ]; + protected $casts = [ + 'label2_asset_logo' => 'boolean', + ]; + /** * Get the app settings. * Cache is expired on Setting model saved in EventServiceProvider. diff --git a/app/Notifications/CheckinAccessoryNotification.php b/app/Notifications/CheckinAccessoryNotification.php index 53be68f58..7735f7dc1 100644 --- a/app/Notifications/CheckinAccessoryNotification.php +++ b/app/Notifications/CheckinAccessoryNotification.php @@ -26,7 +26,6 @@ class CheckinAccessoryNotification extends Notification $this->admin = $checkedInby; $this->note = $note; $this->settings = Setting::getSettings(); - \Log::debug('Constructor for notification fired'); } /** diff --git a/app/Notifications/CheckinAssetNotification.php b/app/Notifications/CheckinAssetNotification.php index 5389c8ddd..05e56a961 100644 --- a/app/Notifications/CheckinAssetNotification.php +++ b/app/Notifications/CheckinAssetNotification.php @@ -2,6 +2,7 @@ namespace App\Notifications; +use App\Helpers\Helper; use App\Models\Asset; use App\Models\Setting; use App\Models\User; diff --git a/app/Observers/AssetObserver.php b/app/Observers/AssetObserver.php index c15c54a56..f42b041fa 100644 --- a/app/Observers/AssetObserver.php +++ b/app/Observers/AssetObserver.php @@ -11,7 +11,7 @@ use Carbon\Carbon; class AssetObserver { /** - * Listen to the User created event. + * Listen to the Asset updating event. This fires automatically every time an existing asset is saved. * * @param Asset $asset * @return void @@ -137,14 +137,14 @@ class AssetObserver public function saving(Asset $asset) { // determine if calculated eol and then calculate it - this should only happen on a new asset - if (is_null($asset->asset_eol_date) && !is_null($asset->purchase_date) && !is_null($asset->model->eol)){ + if (is_null($asset->asset_eol_date) && !is_null($asset->purchase_date) && ($asset->model->eol > 0)){ $asset->asset_eol_date = $asset->purchase_date->addMonths($asset->model->eol)->format('Y-m-d'); $asset->eol_explicit = false; } // determine if explicit and set eol_explicit to true if (!is_null($asset->asset_eol_date) && !is_null($asset->purchase_date)) { - if($asset->model->eol) { + if($asset->model->eol > 0) { $months = Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date); if($months != $asset->model->eol) { $asset->eol_explicit = true; @@ -153,7 +153,7 @@ class AssetObserver } elseif (!is_null($asset->asset_eol_date) && is_null($asset->purchase_date)) { $asset->eol_explicit = true; } - if ((!is_null($asset->asset_eol_date)) && (!is_null($asset->purchase_date)) && (is_null($asset->model->eol))) { + if ((!is_null($asset->asset_eol_date)) && (!is_null($asset->purchase_date)) && (is_null($asset->model->eol) || ($asset->model->eol == 0))) { $asset->eol_explicit = true; } diff --git a/app/Presenters/AssetModelPresenter.php b/app/Presenters/AssetModelPresenter.php index e661b1ab8..85a0fa58e 100644 --- a/app/Presenters/AssetModelPresenter.php +++ b/app/Presenters/AssetModelPresenter.php @@ -104,7 +104,7 @@ class AssetModelPresenter extends Presenter 'searchable' => false, 'sortable' => true, 'switchable' => true, - 'title' => trans('general.eol'), + 'title' => trans('admin/hardware/form.eol_rate'), 'visible' => true, ], [ diff --git a/app/Presenters/AssetPresenter.php b/app/Presenters/AssetPresenter.php index c570c568d..de7c2c770 100644 --- a/app/Presenters/AssetPresenter.php +++ b/app/Presenters/AssetPresenter.php @@ -173,7 +173,7 @@ class AssetPresenter extends Presenter 'searchable' => false, 'sortable' => true, 'visible' => false, - 'title' => trans('general.eol'), + 'title' => trans('admin/hardware/form.eol_rate'), ], [ 'field' => 'asset_eol_date', diff --git a/app/Presenters/LocationPresenter.php b/app/Presenters/LocationPresenter.php index bf4ee27bb..86e82c122 100644 --- a/app/Presenters/LocationPresenter.php +++ b/app/Presenters/LocationPresenter.php @@ -106,7 +106,7 @@ class LocationPresenter extends Presenter 'searchable' => true, 'sortable' => true, 'switchable' => true, - 'title' => trans('admin/locations/table.address'), + 'title' => trans('admin/locations/table.address2'), 'visible' => false, ], [ diff --git a/app/Providers/ValidationServiceProvider.php b/app/Providers/ValidationServiceProvider.php index d7a3c0377..30c5a0197 100644 --- a/app/Providers/ValidationServiceProvider.php +++ b/app/Providers/ValidationServiceProvider.php @@ -48,6 +48,8 @@ class ValidationServiceProvider extends ServiceProvider // Unique only if undeleted // This works around the use case where multiple deleted items have the same unique attribute. // (I think this is a bug in Laravel's validator?) + // $parameters is the rule parameters, like `unique_undeleted:users,id` - $parameters[0] is users, $parameters[1] is id + // the UniqueUndeletedTrait prefills these so you can just use `unique_undeleted` in your rules (but this would only work directly in the model) Validator::extend('unique_undeleted', function ($attribute, $value, $parameters, $validator) { if (count($parameters)) { $count = DB::table($parameters[0])->select('id')->where($attribute, '=', $value)->whereNull('deleted_at')->where('id', '!=', $parameters[1])->count(); @@ -232,6 +234,10 @@ class ValidationServiceProvider extends ServiceProvider return true; } }); + + Validator::extend('not_array', function ($attribute, $value, $parameters, $validator) { + return !is_array($value); + }); } /** diff --git a/app/View/Label.php b/app/View/Label.php index d581548eb..83184e4b0 100644 --- a/app/View/Label.php +++ b/app/View/Label.php @@ -103,19 +103,12 @@ class Label implements View $logo = null; - // Should we be trying to use a logo at all? - if ($settings->label2_asset_logo='1') { - - // If we don't have a company image, fall back to the general site label image - if (!empty($settings->label_logo)) { - $logo = Storage::disk('public')->path('/'.e($settings->label_logo)); - } - - // If we have a company logo, use that first - if (($asset->company) && ($asset->company->image!='')) { - $logo = Storage::disk('public')->path('companies/'.e($asset->company->image)); - } - + // Should we use the assets assigned company logo? (A.K.A. "Is `Labels > Use Asset Logo` enabled?"), and do we have a company logo? + if ($settings->label2_asset_logo && $asset->company && $asset->company->image!='') { + $logo = Storage::disk('public')->path('companies/'.e($asset->company->image)); + } elseif (!empty($settings->label_logo)) { + // Use the general site label logo, if available + $logo = Storage::disk('public')->path('/'.e($settings->label_logo)); } if (!empty($logo)) { diff --git a/composer.json b/composer.json index 57a5fe304..f2a663d33 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ } ], "require": { - "php": "8.1 - 8.2", + "php": "^8.1", "ext-curl": "*", "ext-fileinfo": "*", "ext-json": "*", diff --git a/composer.lock b/composer.lock index e1f6c3f7d..179fb8495 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6a7666957fcf514f54340bf80ad18726", + "content-hash": "6f3c589a1c2a3a3dc24535abba26e1a6", "packages": [ { "name": "alek13/slack", @@ -15699,7 +15699,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "8.0 - 8.2", + "php": "^8.1", "ext-curl": "*", "ext-fileinfo": "*", "ext-json": "*", @@ -15707,5 +15707,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 b220a1255..311cfdb33 100644 --- a/config/version.php +++ b/config/version.php @@ -1,10 +1,10 @@ 'v6.2.3', - 'full_app_version' => 'v6.2.3 - build 11759-g8c4bf74f9', - 'build_version' => '11759', + 'full_app_version' => 'v6.2.3 - build 11936-gb47e734b3', + 'build_version' => '11936', 'prerelease_version' => '', - 'hash_version' => 'g8c4bf74f9', - 'full_hash' => 'v6.2.3-42-g8c4bf74f9', + 'hash_version' => 'gb47e734b3', + 'full_hash' => 'v6.2.3-175-gb47e734b3', 'branch' => 'develop', ); \ No newline at end of file diff --git a/database/factories/ActionlogFactory.php b/database/factories/ActionlogFactory.php index c25fdcc70..1a4007888 100644 --- a/database/factories/ActionlogFactory.php +++ b/database/factories/ActionlogFactory.php @@ -38,7 +38,7 @@ class ActionlogFactory extends Factory { return $this->state(function () { $target = User::inRandomOrder()->first(); - $asset = Asset::RTD()->inRandomOrder()->first(); + $asset = Asset::inRandomOrder()->RTD()->first(); $asset->update( [ diff --git a/database/factories/CustomFieldFactory.php b/database/factories/CustomFieldFactory.php index 2dfa07d61..9407f16b0 100644 --- a/database/factories/CustomFieldFactory.php +++ b/database/factories/CustomFieldFactory.php @@ -26,6 +26,7 @@ class CustomFieldFactory extends Factory 'format' => '', 'element' => 'text', 'auto_add_to_fieldsets' => '0', + 'show_in_requestable_list' => '0', ]; } @@ -66,6 +67,7 @@ class CustomFieldFactory extends Factory return [ 'name' => 'CPU', 'help_text' => 'The speed of the processor on this device.', + 'show_in_requestable_list' => '1', ]; }); } @@ -79,4 +81,28 @@ class CustomFieldFactory extends Factory ]; }); } + + public function testEncrypted() + { + return $this->state(function () { + return [ + 'name' => 'Test Encrypted', + 'field_encrypted' => '1', + 'help_text' => 'This is a sample encrypted field.', + ]; + }); + } + + public function testCheckbox() + { + return $this->state(function () { + return [ + 'name' => 'Test Checkbox', + 'help_text' => 'This is a sample checkbox.', + 'field_values' => "One\nTwo\nThree", + 'element' => 'checkbox', + ]; + }); + } + } diff --git a/database/migrations/2023_10_25_064324_add_show_in_requestable_to_custom_fields.php b/database/migrations/2023_10_25_064324_add_show_in_requestable_to_custom_fields.php new file mode 100644 index 000000000..710a56e81 --- /dev/null +++ b/database/migrations/2023_10_25_064324_add_show_in_requestable_to_custom_fields.php @@ -0,0 +1,34 @@ +boolean('show_in_requestable_list')->after('show_in_email')->nullable()->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('custom_fields', function (Blueprint $table) { + if (Schema::hasColumn('custom_fields', 'show_in_requestable_list')) { + $table->dropColumn('show_in_requestable_list'); + } + }); + } +} diff --git a/database/seeders/CustomFieldSeeder.php b/database/seeders/CustomFieldSeeder.php index 551e05f40..e51ca510f 100644 --- a/database/seeders/CustomFieldSeeder.php +++ b/database/seeders/CustomFieldSeeder.php @@ -33,6 +33,9 @@ class CustomFieldSeeder extends Seeder CustomField::factory()->count(1)->ram()->create(); CustomField::factory()->count(1)->cpu()->create(); CustomField::factory()->count(1)->macAddress()->create(); + CustomField::factory()->count(1)->testEncrypted()->create(); + CustomField::factory()->count(1)->testCheckbox()->create(); + DB::table('custom_field_custom_fieldset')->insert([ [ @@ -66,6 +69,33 @@ class CustomFieldSeeder extends Seeder 'required' => 0, ], + [ + 'custom_field_id' => '6', + 'custom_fieldset_id' => '2', + 'order' => 0, + 'required' => 0, + ], + + [ + 'custom_field_id' => '6', + 'custom_fieldset_id' => '1', + 'order' => 0, + 'required' => 0, + ], + + [ + 'custom_field_id' => '7', + 'custom_fieldset_id' => '2', + 'order' => 0, + 'required' => 0, + ], + [ + 'custom_field_id' => '7', + 'custom_fieldset_id' => '1', + 'order' => 0, + 'required' => 0, + ], + ]); } } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 142960413..5e26a9a25 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -38,12 +38,13 @@ class DatabaseSeeder extends Seeder $this->call(DepreciationSeeder::class); $this->call(StatuslabelSeeder::class); $this->call(AccessorySeeder::class); + $this->call(CustomFieldSeeder::class); $this->call(AssetSeeder::class); $this->call(LicenseSeeder::class); $this->call(ComponentSeeder::class); $this->call(ConsumableSeeder::class); $this->call(ActionlogSeeder::class); - $this->call(CustomFieldSeeder::class); + Artisan::call('snipeit:sync-asset-locations', ['--output' => 'all']); $output = Artisan::output(); diff --git a/docker-compose.yml b/docker-compose.yml index 101c15d3b..15272ce5c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ services: ports: - "8000:80" volumes: - - ./logs:/var/www/html/storage/logs + - ./storage/logs:/var/www/html/storage/logs depends_on: - mariadb - redis diff --git a/resources/lang/en/admin/accessories/message.php b/resources/lang/en/admin/accessories/message.php index 542f71f03..c688d5e03 100644 --- a/resources/lang/en/admin/accessories/message.php +++ b/resources/lang/en/admin/accessories/message.php @@ -3,6 +3,7 @@ return array( 'does_not_exist' => 'The accessory [:id] does not exist.', + 'not_found' => 'That accessory was not found.', 'assoc_users' => 'This accessory currently has :count items checked out to users. Please check in the accessories and and try again. ', 'create' => array( diff --git a/resources/lang/en/admin/custom_fields/general.php b/resources/lang/en/admin/custom_fields/general.php index cb4ab3730..e3c21d1f0 100644 --- a/resources/lang/en/admin/custom_fields/general.php +++ b/resources/lang/en/admin/custom_fields/general.php @@ -34,7 +34,8 @@ return [ 'create_field' => 'New Custom Field', 'create_field_title' => 'Create a new custom field', 'value_encrypted' => 'The value of this field is encrypted in the database. Only admin users will be able to view the decrypted value', - 'show_in_email' => 'Include the value of this field in checkout emails sent to the user? Encrypted fields cannot be included in emails.', + 'show_in_email' => 'Include the value of this field in checkout emails sent to the user? Encrypted fields cannot be included in emails', + 'show_in_email_short' => 'Include in emails.', 'help_text' => 'Help Text', 'help_text_description' => 'This is optional text that will appear below the form elements while editing an asset to provide context on the field.', 'about_custom_fields_title' => 'About Custom Fields', @@ -51,7 +52,10 @@ return [ 'display_in_user_view_table' => 'Visible to User', 'auto_add_to_fieldsets' => 'Automatically add this to every new fieldset', 'add_to_preexisting_fieldsets' => 'Add to any existing fieldsets', - 'show_in_listview' => 'Show in list views by default. Authorized users will still be able to show/hide via the column selector.', + 'show_in_listview' => 'Show in list views by default. Authorized users will still be able to show/hide via the column selector', 'show_in_listview_short' => 'Show in lists', + 'show_in_requestable_list_short' => 'Show in requestable assets list', + 'show_in_requestable_list' => 'Show value in requestable assets list. Encrypted fields will not be shown', + 'encrypted_options' => 'This field is encrypted, so some display options will not be available.', ]; diff --git a/resources/lang/en/admin/hardware/form.php b/resources/lang/en/admin/hardware/form.php index ef877c837..ee3fa20fb 100644 --- a/resources/lang/en/admin/hardware/form.php +++ b/resources/lang/en/admin/hardware/form.php @@ -49,6 +49,7 @@ return [ 'asset_location' => 'Update Asset Location', 'asset_location_update_default_current' => 'Update default location AND actual location', 'asset_location_update_default' => 'Update only default location', + 'asset_location_update_actual' => 'Update only actual location', 'asset_not_deployable' => 'That asset status is not deployable. This asset cannot be checked out.', 'asset_deployable' => 'That status is deployable. This asset can be checked out.', 'processing_spinner' => 'Processing... (This might take a bit of time on large files)', diff --git a/resources/lang/en/admin/hardware/message.php b/resources/lang/en/admin/hardware/message.php index 04be92a75..056692998 100644 --- a/resources/lang/en/admin/hardware/message.php +++ b/resources/lang/en/admin/hardware/message.php @@ -11,6 +11,7 @@ return [ 'create' => [ 'error' => 'Asset was not created, please try again. :(', 'success' => 'Asset created successfully. :)', + 'success_linked' => 'Asset with tag :tag was created successfully. Click here to view.', ], 'update' => [ diff --git a/resources/lang/en/admin/locations/table.php b/resources/lang/en/admin/locations/table.php index 29edf0f56..0cfaa4fdc 100644 --- a/resources/lang/en/admin/locations/table.php +++ b/resources/lang/en/admin/locations/table.php @@ -15,6 +15,7 @@ return [ 'print_all_assigned' => 'Print All Assigned', 'name' => 'Location Name', 'address' => 'Address', + 'address2' => 'Address Line 2', 'zip' => 'Postal Code', 'locations' => 'Locations', 'parent' => 'Parent', diff --git a/resources/lang/en/admin/reports/general.php b/resources/lang/en/admin/reports/general.php index 344d5c874..9b682f8ec 100644 --- a/resources/lang/en/admin/reports/general.php +++ b/resources/lang/en/admin/reports/general.php @@ -6,5 +6,12 @@ return [ 'send_reminder' => 'Send reminder', 'reminder_sent' => 'Reminder sent', 'acceptance_deleted' => 'Acceptance request deleted', - 'acceptance_request' => 'Acceptance request' + 'acceptance_request' => 'Acceptance request', + 'custom_export' => [ + 'user_address' => 'User Address', + 'user_city' => 'User City', + 'user_state' => 'User State', + 'user_country' => 'User Country', + 'user_zip' => 'User Zip' + ] ]; \ No newline at end of file diff --git a/resources/lang/en/admin/users/message.php b/resources/lang/en/admin/users/message.php index a3f936dcb..b7c0a29f1 100644 --- a/resources/lang/en/admin/users/message.php +++ b/resources/lang/en/admin/users/message.php @@ -8,6 +8,7 @@ return array( 'user_exists' => 'User already exists!', 'user_not_found' => 'User does not exist.', 'user_login_required' => 'The login field is required', + 'user_has_no_assets_assigned' => 'No assets currently assigned to user.', 'user_password_required' => 'The password is required.', 'insufficient_permissions' => 'Insufficient Permissions.', 'user_deleted_warning' => 'This user has been deleted. You will have to restore this user to edit them or assign them new assets.', diff --git a/resources/lang/en/general.php b/resources/lang/en/general.php index e1a5cc0e7..fba828839 100644 --- a/resources/lang/en/general.php +++ b/resources/lang/en/general.php @@ -368,7 +368,7 @@ return [ 'consumables_count' => 'Consumables Count', 'components_count' => 'Components Count', 'licenses_count' => 'Licenses Count', - 'notification_error' => 'Error:', + 'notification_error' => 'Error', 'notification_error_hint' => 'Please check the form below for errors', 'notification_bulk_error_hint' => 'The following fields had validation errors and were not edited:', 'notification_success' => 'Success', diff --git a/resources/lang/en/help.php b/resources/lang/en/help.php index a3a2ddd76..a59e0056b 100644 --- a/resources/lang/en/help.php +++ b/resources/lang/en/help.php @@ -30,5 +30,6 @@ return [ 'consumables' => 'Consumables are anything purchased that will be used up over time. For example, printer ink or copier paper.', 'depreciations' => 'You can set up asset depreciations to depreciate assets based on straight-line depreciation.', - + + 'empty_file' => 'The importer detects that this file is empty.' ]; diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index df514da6f..bb35515fd 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -95,6 +95,7 @@ return [ 'url' => 'The :attribute format is invalid.', 'unique_undeleted' => 'The :attribute must be unique.', 'non_circular' => 'The :attribute must not create a circular reference.', + 'not_array' => 'The :attribute field cannot be an array.', 'disallow_same_pwd_as_user_fields' => 'Password cannot be the same as the username.', 'letters' => 'Password must contain at least one letter.', 'numbers' => 'Password must contain at least one number.', diff --git a/resources/views/account/accept/index.blade.php b/resources/views/account/accept/index.blade.php index b627135a4..77cf21664 100755 --- a/resources/views/account/accept/index.blade.php +++ b/resources/views/account/accept/index.blade.php @@ -24,7 +24,7 @@ data-side-pagination="client" data-show-columns="true" data-show-export="true" - data-show-refresh="true" + data-show-refresh="false" data-sort-order="asc" id="pendingAcceptances" class="table table-striped snipe-table" diff --git a/resources/views/account/requestable-assets.blade.php b/resources/views/account/requestable-assets.blade.php index adec7a100..8e3c08fd5 100644 --- a/resources/views/account/requestable-assets.blade.php +++ b/resources/views/account/requestable-assets.blade.php @@ -65,6 +65,12 @@ {{ trans('admin/hardware/table.location') }} {{ trans('admin/hardware/table.status') }} {{ trans('admin/hardware/form.expected_checkin') }} + + @foreach(\App\Models\CustomField::get() as $field) + @if (($field->field_encrypted=='0') && ($field->show_in_requestable_list=='1')) + {{ $field->name }} + @endif + @endforeach {{ trans('table.actions') }} diff --git a/resources/views/account/requested.blade.php b/resources/views/account/requested.blade.php index e3d80edf6..e2e1db2e5 100644 --- a/resources/views/account/requested.blade.php +++ b/resources/views/account/requested.blade.php @@ -32,13 +32,19 @@ }'> - {{ trans('general.image') }} - {{ trans('general.item_name') }} - {{ trans('general.type') }} - {{ trans('general.qty') }} - {{ trans('admin/hardware/table.location') }} - {{ trans('admin/hardware/form.expected_checkin') }} - {{ trans('general.requested_date') }} + {{ trans('general.image') }} + {{ trans('general.item_name') }} + {{ trans('general.type') }} + {{ trans('general.qty') }} + {{ trans('admin/hardware/table.location') }} + {{ trans('admin/hardware/form.expected_checkin') }} + {{ trans('general.requested_date') }} + + @foreach(\App\Models\CustomField::get() as $field) + @if (($field->field_encrypted=='0') && ($field->show_in_requestable_list=='1')) + {{ $field->name }} + @endif + @endforeach diff --git a/resources/views/account/view-assets.blade.php b/resources/views/account/view-assets.blade.php index 10b09602a..81011175a 100755 --- a/resources/views/account/view-assets.blade.php +++ b/resources/views/account/view-assets.blade.php @@ -388,7 +388,7 @@ data-show-columns="true" data-show-export="true" data-show-footer="true" - data-show-refresh="true" + data-show-refresh="false" data-sort-order="asc" id="userAssets" class="table table-striped snipe-table" @@ -478,7 +478,7 @@ data-side-pagination="client" data-show-columns="true" data-show-export="true" - data-show-refresh="true" + data-show-refresh="false" data-sort-order="asc" id="userLicenses" class="table table-striped snipe-table" @@ -525,7 +525,7 @@ data-show-fullscreen="true" data-show-export="true" data-show-footer="true" - data-show-refresh="true" + data-show-refresh="false" data-sort-order="asc" data-sort-name="name" class="table table-striped snipe-table table-hover" @@ -576,7 +576,7 @@ data-show-fullscreen="true" data-show-export="true" data-show-footer="true" - data-show-refresh="true" + data-show-refresh="false" data-sort-order="asc" data-sort-name="name" class="table table-striped snipe-table table-hover" diff --git a/resources/views/custom_fields/fields/edit.blade.php b/resources/views/custom_fields/fields/edit.blade.php index ac945d859..504b556fa 100644 --- a/resources/views/custom_fields/fields/edit.blade.php +++ b/resources/views/custom_fields/fields/edit.blade.php @@ -88,7 +88,7 @@ } @endphp
- {{ Form::select("format",Helper::predefined_formats(), ($field_format == '') ? $field->format : $field_format, array('class'=>'format select2 form-control', 'aria-label'=>'format')) }} + {{ Form::select("format",Helper::predefined_formats(), ($field_format == '') ? $field->format : $field_format, array('class'=>'format select2 form-control', 'aria-label'=>'format', 'style' => 'width:100%;')) }} {!! $errors->first('format', '') !!}
@@ -118,10 +118,40 @@ + +
+ + + @if (($field->id) && ($field->field_encrypted=='1')) +
+
+ + {{ trans('general.notification_warning') }}: + {{ trans('admin/custom_fields/general.encrypted_options') }} +
+ +
+ @endif + + @if (!$field->id) + +
+ +
+ + + @endif + -
+
- @if (!$field->id) - -
- -
- - @endif + @if ((!$field->id) || ($field->field_encrypted=='0')) + + +
+ +
-
+
+ +
+ +
+ @endif + -
+
- -
- -
+
@@ -287,11 +316,13 @@ $("#show_in_email").hide(); $("#display_in_user_view").hide(); $("#is_unique").hide(); + $("#show_in_requestable_list").hide(); } else { $("#encrypt_warning").hide(); $("#show_in_email").show(); $("#display_in_user_view").show(); $("#is_unique").show(); + $("#show_in_requestable_list").show(); } }); diff --git a/resources/views/custom_fields/index.blade.php b/resources/views/custom_fields/index.blade.php index 141d71534..08f2fb384 100644 --- a/resources/views/custom_fields/index.blade.php +++ b/resources/views/custom_fields/index.blade.php @@ -145,12 +145,14 @@ - + - - - {{ trans('admin/custom_fields/general.field_element_short') }} + + + + {{ trans('admin/custom_fields/general.unique') }} + {{ trans('admin/custom_fields/general.field_element_short') }} {{ trans('admin/custom_fields/general.fieldsets') }} {{ trans('button.actions') }} @@ -161,7 +163,7 @@ {{ $field->name }} {{ $field->help_text }} - {!! ($field->is_unique=='1') ? '' : '' !!} + {!! ($field->is_unique=='1') ? '' : '' !!} {{ $field->convertUnicodeDbSlug() }} @if ($field->convertUnicodeDbSlug()!=$field->db_column) @@ -170,10 +172,12 @@ @endif {{ $field->format }} - {!! ($field->field_encrypted=='1' ? '' : '') !!} - {!! ($field->show_in_listview=='1' ? '' : '') !!} - {!! ($field->display_in_user_view=='1' ? '' : '') !!} - {!! ($field->show_in_email=='1') ? '' : '' !!} + {!! ($field->field_encrypted=='1' ? '' : '') !!} + {!! ($field->show_in_listview=='1' ? '' : '') !!} + {!! ($field->display_in_user_view=='1' ? '' : '') !!} + {!! ($field->show_in_email=='1') ? '' : '' !!} + {!! ($field->show_in_requestable_list=='1') ? '' : '' !!} + {!! ($field->is_unique=='1') ? '' : '' !!} {{ $field->element }} @foreach($field->fieldset as $fieldset) diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index 296a940b3..274494687 100755 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -274,63 +274,136 @@
- -
-
-

{{ trans('general.asset') }} {{ trans('general.locations') }}

-
- -
-
- -
-
-
-
- - - - - - - - - - - -
{{ trans('general.name') }} - - {{ trans('general.asset_count') }} - - - {{ trans('general.assigned') }} - - - {{ trans('general.people') }} - -
-
-
- -
+ @if ($snipeSettings->full_multiple_companies_support=='1') + +
+
+

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

+
+ +
+
+ +
+
+
+
+ - - + + + + + + + + + + + +
{{ trans('general.name') }} + + {{ trans('general.people') }} + + + {{ trans('general.asset_count') }} + + + {{ trans('general.accessories_count') }} + + + {{ trans('general.consumables_count') }} + + + {{ trans('general.components_count') }} + + + {{ trans('general.licenses_count') }} +
+
+
+ +
+ +
+
+ + @else + +
+
+

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

+
+ +
+
+ +
+
+
+
+ + + + + + + + + + + + +
{{ trans('general.name') }} + + {{ trans('general.asset_count') }} + + + {{ trans('general.assigned') }} + + + {{ trans('general.people') }} + +
+
+
+ +
+ +
+
+ + @endif +
diff --git a/resources/views/hardware/bulk.blade.php b/resources/views/hardware/bulk.blade.php index 667126ec9..a7e52dfa5 100755 --- a/resources/views/hardware/bulk.blade.php +++ b/resources/views/hardware/bulk.blade.php @@ -62,7 +62,7 @@
@@ -92,9 +92,13 @@ {{ Form::radio('update_real_loc', '1', old('update_real_loc'), ['checked'=> 'checked', 'aria-label'=>'update_real_loc']) }} {{ trans('admin/hardware/form.asset_location_update_default_current') }} +
diff --git a/resources/views/hardware/index.blade.php b/resources/views/hardware/index.blade.php index a9a0dba58..d30a1c012 100755 --- a/resources/views/hardware/index.blade.php +++ b/resources/views/hardware/index.blade.php @@ -34,7 +34,7 @@ {{ trans('general.assets') }} @if (Request::has('order_number')) - : Order #{{ Request::get('order_number') }} + : Order #{{ strval(Request::get('order_number')) }} @endif @stop @@ -88,7 +88,7 @@ class="table table-striped snipe-table" data-url="{{ route('api.assets.index', array('status' => e(Request::get('status')), - 'order_number'=>e(Request::get('order_number')), + 'order_number'=>e(strval(Request::get('order_number'))), 'company_id'=>e(Request::get('company_id')), 'status_id'=>e(Request::get('status_id')))) }}" data-export-options='{ diff --git a/resources/views/hardware/view.blade.php b/resources/views/hardware/view.blade.php index c4c93555f..2a4cf78aa 100755 --- a/resources/views/hardware/view.blade.php +++ b/resources/views/hardware/view.blade.php @@ -631,7 +631,7 @@
@endif - @if (($asset->model) && ($asset->model->eol)) + @if (($asset->asset_eol_date) && ($asset->purchase_date))
@@ -639,7 +639,7 @@
- {{ $asset->model->eol }} + {{ Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date) }} {{ trans('admin/hardware/form.months') }}
@@ -650,6 +650,9 @@
{{ trans('admin/hardware/form.eol_date') }} + @if ($asset->purchase_date) + {!! $asset->asset_eol_date < date("Y-m-d") ? '' : '' !!} + @endif
diff --git a/resources/views/layouts/basic.blade.php b/resources/views/layouts/basic.blade.php index b921042b0..759ffd52a 100644 --- a/resources/views/layouts/basic.blade.php +++ b/resources/views/layouts/basic.blade.php @@ -8,7 +8,7 @@ {{ ($snipeSettings) && ($snipeSettings->site_name) ? $snipeSettings->site_name : 'Snipe-IT' }} - + {{-- stylesheets --}} diff --git a/resources/views/licenses/edit.blade.php b/resources/views/licenses/edit.blade.php index cdbbc23e7..b0ff9063f 100755 --- a/resources/views/licenses/edit.blade.php +++ b/resources/views/licenses/edit.blade.php @@ -79,7 +79,7 @@
- +
{!! $errors->first('expiration_date', '') !!} @@ -93,7 +93,7 @@
- +
{!! $errors->first('termination_date', '') !!} diff --git a/resources/views/livewire/importer.blade.php b/resources/views/livewire/importer.blade.php index 297dcc7b8..0134cfe7f 100644 --- a/resources/views/livewire/importer.blade.php +++ b/resources/views/livewire/importer.blade.php @@ -235,9 +235,16 @@ ]) }}
+ @if ($activeFile->first_row)

{{ str_limit($activeFile->first_row[$index], 50, '...') }}

+ @else + @php + $statusText = trans('help.empty_file'); + $statusType = 'info'; + @endphp + @endif
@endforeach @else diff --git a/resources/views/locations/view.blade.php b/resources/views/locations/view.blade.php index a1c332019..e000ed5c2 100644 --- a/resources/views/locations/view.blade.php +++ b/resources/views/locations/view.blade.php @@ -104,6 +104,17 @@ + +
  • + + + + +
  • @@ -319,6 +330,51 @@
    +
    +

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

    + +
    +
    + + + + + + + + + + + + + + + +
    {{ trans('general.date') }}{{ trans('general.admin') }}{{ trans('general.action') }}{{ trans('general.item') }}{{ trans('general.target') }}{{ trans('general.notes') }}{{ trans('general.signature') }}{{ trans('general.download') }}{{ trans('admin/hardware/table.changed')}}
    +
    +
    +
    +
    diff --git a/resources/views/manufacturers/index.blade.php b/resources/views/manufacturers/index.blade.php index 1f9ca175a..e7afd91dd 100755 --- a/resources/views/manufacturers/index.blade.php +++ b/resources/views/manufacturers/index.blade.php @@ -46,7 +46,7 @@ data-sort-order="asc" id="manufacturersTable" class="table table-striped snipe-table" - data-url="{{route('api.manufacturers.index', ['deleted' => e(Request::get('deleted')) ]) }}" + data-url="{{route('api.manufacturers.index', ['deleted' => (request('deleted')=='true') ? 'true' : 'false' ]) }}" data-export-options='{ "fileName": "export-manufacturers-{{ date('Y-m-d') }}", "ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"] diff --git a/resources/views/models/custom_fields_form_bulk_edit.blade.php b/resources/views/models/custom_fields_form_bulk_edit.blade.php index e974c3f6e..f30c60d33 100644 --- a/resources/views/models/custom_fields_form_bulk_edit.blade.php +++ b/resources/views/models/custom_fields_form_bulk_edit.blade.php @@ -37,21 +37,20 @@ @elseif ($field->element=='checkbox') @foreach ($field->formatFieldValuesAsArray() as $key => $value) -
    - -
    + + @endforeach @elseif ($field->element=='radio') @foreach ($field->formatFieldValuesAsArray() as $value) -
    -
    + @endforeach @endif diff --git a/resources/views/models/view.blade.php b/resources/views/models/view.blade.php index 99a22b00e..29cfd99c1 100755 --- a/resources/views/models/view.blade.php +++ b/resources/views/models/view.blade.php @@ -236,6 +236,12 @@ @endif + @if ($model->min_amt) +
  • {{ trans('general.min_amt') }}: + {{$model->min_amt }} +
  • + @endif + @if ($model->manufacturer)
  • {{ trans('general.manufacturer') }}: diff --git a/resources/views/notifications.blade.php b/resources/views/notifications.blade.php index 1811b3cdd..0205e3e10 100755 --- a/resources/views/notifications.blade.php +++ b/resources/views/notifications.blade.php @@ -35,13 +35,25 @@ @endif +@if ($message = Session::get('success-unescaped')) +
    +
    + + + {{ trans('general.notification_success') }}: + {!! $message !!} +
    +
    +@endif + + @if ($assets = Session::get('assets')) @foreach ($assets as $asset)
    - {{ trans('general.asset_information') }} + {{ trans('general.asset_information') }}:
      @isset ($asset->model->name)
    • {{ trans('general.model_name') }} {{ $asset->model->name }}
    • @@ -67,7 +79,7 @@
      - {{ trans('general.consumable_information') }} + {{ trans('general.consumable_information') }}:
      • {{ trans('general.consumable_name') }} {{ $consumable->name }}
    @@ -81,7 +93,7 @@
    - {{ trans('general.accessory_information') }} + {{ trans('general.accessory_information') }}:
    • {{ trans('general.accessory_name') }} {{ $accessory->name }}
    @@ -94,7 +106,7 @@
    - {{ trans('general.error') }} + {{ trans('general.error') }}: {{ $message }}
    @@ -107,7 +119,7 @@
    - {{ trans('general.notification_error') }} + {{ trans('general.notification_error') }}: {{ $message }}
    @@ -115,17 +127,19 @@ @endif -@if ($messages = Session::get('bulk_errors')) +@if ($messages = Session::get('bulk_asset_errors'))
    {{ trans('general.notification_error') }}: {{ trans('general.notification_bulk_error_hint') }} - @foreach($messages as $message) + @foreach($messages as $key => $message) + @for ($x = 0; $x < count($message); $x++)
      -
    • {{ $message }}
    • -
    +
  • {{ $message[$x] }}
  • + + @endfor @endforeach diff --git a/resources/views/partials/bootstrap-table.blade.php b/resources/views/partials/bootstrap-table.blade.php index 78f3d152d..0122096f8 100644 --- a/resources/views/partials/bootstrap-table.blade.php +++ b/resources/views/partials/bootstrap-table.blade.php @@ -530,15 +530,37 @@ function changeLogFormatter(value) { + var result = ''; + var pretty_index = ''; + for (var index in value) { - result += index + ': ' + value[index].old + ' ' + value[index].new + '
    ' + + + // Check if it's a custom field + if (index.startsWith('_snipeit_')) { + pretty_index = index.replace("_snipeit_", "Custom:_"); + } else { + pretty_index = index; + } + + extra_pretty_index = prettyLog(pretty_index); + + result += extra_pretty_index + ': ' + value[index].old + ' ' + value[index].new + '
    ' } return result; } + function prettyLog(str) { + let frags = str.split('_'); + for (let i = 0; i < frags.length; i++) { + frags[i] = frags[i].charAt(0).toUpperCase() + frags[i].slice(1); + } + return frags.join(' '); + } + // Create a linked phone number in the table list function phoneFormatter(value) { diff --git a/resources/views/partials/forms/edit/eol_date.blade.php b/resources/views/partials/forms/edit/eol_date.blade.php index 88055cfc0..fb461cf44 100644 --- a/resources/views/partials/forms/edit/eol_date.blade.php +++ b/resources/views/partials/forms/edit/eol_date.blade.php @@ -1,9 +1,9 @@ - +
    - +
    {!! $errors->first('asset_eol_date', '') !!} diff --git a/resources/views/partials/label2-field-definitions.blade.php b/resources/views/partials/label2-field-definitions.blade.php index 6522b951d..21aa7a0cf 100644 --- a/resources/views/partials/label2-field-definitions.blade.php +++ b/resources/views/partials/label2-field-definitions.blade.php @@ -304,7 +304,54 @@ - +
    @@ -331,4 +378,4 @@ >
    - \ No newline at end of file + diff --git a/resources/views/reports/custom.blade.php b/resources/views/reports/custom.blade.php index 5b0bd384e..cc0ec158a 100644 --- a/resources/views/reports/custom.blade.php +++ b/resources/views/reports/custom.blade.php @@ -221,6 +221,38 @@ {{ trans('admin/users/table.title') }} + + + + + + + + + + + + + + @if ($customfields->count() > 0) @@ -259,79 +291,131 @@
    -
    +
    -
    +
    -
    - +
    + to - +
    + + @if ($errors->has('purchase_start') || $errors->has('purchase_end')) +
    + {!! $errors->first('purchase_start', '') !!} + {!! $errors->first('purchase_end', '') !!} +
    + @endif +
    -
    +
    -
    - +
    + to - +
    + + @if ($errors->has('created_start') || $errors->has('created_end')) +
    + {!! $errors->first('created_start', '') !!} + {!! $errors->first('created_end', '') !!} +
    + @endif
    -
    +
    -
    - +
    + to - +
    + + @if ($errors->has('checkout_date_start') || $errors->has('checkout_date_end')) +
    + {!! $errors->first('checkout_date_start', '') !!} + {!! $errors->first('checkout_date_end', '') !!} +
    + @endif +
    -
    +
    -
    - +
    + {{ strtolower(trans('general.to')) }} - +
    + + @if ($errors->has('checkin_date_start') || $errors->has('checkin_date_end')) +
    + {!! $errors->first('checkin_date_start', '') !!} + {!! $errors->first('checkin_date_end', '') !!} +
    + @endif
    -
    +
    -
    - +
    + to - +
    + + @if ($errors->has('expected_checkin_start') || $errors->has('expected_checkin_end')) +
    + {!! $errors->first('expected_checkin_start', '') !!} + {!! $errors->first('expected_checkin_end', '') !!} +
    + @endif +
    -
    +
    -
    - +
    + to - +
    + + @if ($errors->has('last_audit_start') || $errors->has('last_audit_end')) +
    + {!! $errors->first('last_audit_start', '') !!} + {!! $errors->first('last_audit_end', '') !!} +
    + @endif
    -
    +
    -
    - +
    + to - +
    + + @if ($errors->has('next_audit_start') || $errors->has('next_audit_end')) +
    + {!! $errors->first('next_audit_start', '') !!} + {!! $errors->first('next_audit_end', '') !!} +
    + @endif
    diff --git a/resources/views/settings/backups.blade.php b/resources/views/settings/backups.blade.php index ea074d45a..67c197654 100644 --- a/resources/views/settings/backups.blade.php +++ b/resources/views/settings/backups.blade.php @@ -137,7 +137,7 @@ @csrf -
    +
    @@ -145,26 +145,18 @@ - +
    - -

    -

    {{ trans_choice('general.filetypes_accepted_help', 1, ['size' => Helper::file_upload_max_size_readable(), 'types' => '.zip']) }}

    - {!! $errors->first('image', '') !!} - - + {!! $errors->first('file', '') !!} +
    diff --git a/resources/views/settings/labels.blade.php b/resources/views/settings/labels.blade.php index 46ce4fe61..a6edd2915 100644 --- a/resources/views/settings/labels.blade.php +++ b/resources/views/settings/labels.blade.php @@ -216,7 +216,7 @@ {{ Form::label('label2_fields', trans('admin/settings/general.label2_fields')) }}
    - @include('partials.label2-field-definitions', [ 'name' => 'label2_fields', 'value' => old('label2_fields', $setting->label2_fields) ]) + @include('partials.label2-field-definitions', [ 'name' => 'label2_fields', 'value' => old('label2_fields', $setting->label2_fields), 'customFields' => $customFields ]) {!! $errors->first('label2_fields', '') !!}

    {{ trans('admin/settings/general.label2_fields_help') }}

    diff --git a/resources/views/users/bulk-edit.blade.php b/resources/views/users/bulk-edit.blade.php index bdc1ad928..b28eb602d 100644 --- a/resources/views/users/bulk-edit.blade.php +++ b/resources/views/users/bulk-edit.blade.php @@ -39,6 +39,16 @@ @include ('partials.forms.edit.department-select', ['translated_name' => trans('general.department'), 'fieldname' => 'department_id']) +
    +
    + +
    +
    + + @include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'location_id']) @@ -55,11 +65,31 @@ @if (\App\Models\Company::canManageUsersCompanies()) @include ('partials.forms.edit.company-select', ['translated_name' => trans('general.select_company'), 'fieldname' => 'company_id']) + +
    +
    + +
    +
    + @endif @include ('partials.forms.edit.user-select', ['translated_name' => trans('admin/users/table.manager'), 'fieldname' => 'manager_id']) +
    +
    + +
    +
    + +
    diff --git a/resources/views/users/confirm-merge.blade.php b/resources/views/users/confirm-merge.blade.php index ce033acae..45635dc4f 100644 --- a/resources/views/users/confirm-merge.blade.php +++ b/resources/views/users/confirm-merge.blade.php @@ -48,6 +48,7 @@ {{ trans('general.name') }} {{ trans('general.email') }} {{ trans('general.username') }} + {{ trans('general.employee_number') }} {{ trans('general.groups') }} @@ -80,6 +81,9 @@ {{ $user->username }} + + {{ $user->employee_num }} + @foreach ($user->groups as $group) diff --git a/resources/views/users/ldap.blade.php b/resources/views/users/ldap.blade.php index 0dbbbc2fa..555dc6582 100644 --- a/resources/views/users/ldap.blade.php +++ b/resources/views/users/ldap.blade.php @@ -29,7 +29,7 @@
    - @include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'location_id']) + @include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'location_id[]', 'multiple' => true])
    diff --git a/resources/views/users/print.blade.php b/resources/views/users/print.blade.php index 0f1e8a909..3be7923d8 100644 --- a/resources/views/users/print.blade.php +++ b/resources/views/users/print.blade.php @@ -80,8 +80,8 @@ {{ $counter }} {{ $asset->asset_tag }} {{ $asset->name }} - {{ $asset->model->category->name }} - {{ $asset->model->name }} + {{ (($asset->model) && ($asset->model->category)) ? $asset->model->category->name : trans('general.invalid_category') }} + {{ ($asset->model) ? $asset->model->name : trans('general.invalid_model') }} {{ $asset->serial }} {{ $asset->last_checkout }} diff --git a/routes/web.php b/routes/web.php index 0aacf8513..635cdbcb9 100644 --- a/routes/web.php +++ b/routes/web.php @@ -224,6 +224,11 @@ Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'authorize:superuser [SettingsController::class, 'postUploadBackup'] )->name('settings.backups.upload'); + // Handle redirect from after POST request from backup restore + Route::get('/restore/{filename?}', function () { + return redirect(route('settings.backups.index')); + }); + Route::get('/', [SettingsController::class, 'getBackups'])->name('settings.backups.index'); }); diff --git a/routes/web/hardware.php b/routes/web/hardware.php index 1643e5794..d4f289228 100644 --- a/routes/web/hardware.php +++ b/routes/web/hardware.php @@ -122,9 +122,10 @@ Route::group( [AssetCheckinController::class, 'store'] )->name('hardware.checkin.store'); - Route::get('{assetId}/view', - [AssetsController::class, 'show'] - )->name('hardware.view'); + // Redirect old legacy /asset_id/view urls to the resource route version + Route::get('{assetId}/view', function ($assetId) { + return redirect()->route('hardware.show', ['hardware' => $assetId]); + }); Route::get('{assetId}/qr_code', [AssetsController::class, 'getQrCode'] @@ -178,13 +179,17 @@ Route::group( Route::post('bulkcheckout', [BulkAssetsController::class, 'storeCheckout'] )->name('hardware.bulkcheckout.store'); + }); Route::resource('hardware', AssetsController::class, [ 'middleware' => ['auth'], - 'parameters' => ['asset' => 'asset_id' + 'parameters' => ['asset' => 'asset_id', + 'names' => [ + 'show' => 'view', + ], ], ]); diff --git a/tests/Feature/Api/Assets/AssetStoreTest.php b/tests/Feature/Api/Assets/AssetStoreTest.php new file mode 100644 index 000000000..5a68aebcc --- /dev/null +++ b/tests/Feature/Api/Assets/AssetStoreTest.php @@ -0,0 +1,19 @@ +actingAsForApi(User::factory()->create()) + ->postJson(route('api.assets.store')) + ->assertForbidden(); + } +} diff --git a/tests/Feature/Checkouts/LicenseCheckoutTest.php b/tests/Feature/Checkouts/LicenseCheckoutTest.php new file mode 100644 index 000000000..978fac28f --- /dev/null +++ b/tests/Feature/Checkouts/LicenseCheckoutTest.php @@ -0,0 +1,62 @@ +superuser()->create(); + $asset = Asset::factory()->create(); + $licenseSeat = LicenseSeat::factory()->create(); + + $this->actingAs($admin) + ->post("/licenses/{$licenseSeat->license->id}/checkout", [ + 'checkout_to_type' => 'asset', + 'assigned_to' => null, + 'asset_id' => $asset->id, + 'notes' => 'oh hi there', + ]); + + $this->assertDatabaseHas('action_logs', [ + 'action_type' => 'checkout', + 'target_id' => $asset->id, + 'target_type' => Asset::class, + 'item_id' => $licenseSeat->license->id, + 'item_type' => License::class, + 'note' => 'oh hi there', + ]); + } + + public function testNotesAreStoredInActionLogOnCheckoutToUser() + { + $admin = User::factory()->superuser()->create(); + $licenseSeat = LicenseSeat::factory()->create(); + + $this->actingAs($admin) + ->post("/licenses/{$licenseSeat->license->id}/checkout", [ + 'checkout_to_type' => 'user', + 'assigned_to' => $admin->id, + 'asset_id' => null, + 'notes' => 'oh hi there', + ]); + + $this->assertDatabaseHas('action_logs', [ + 'action_type' => 'checkout', + 'target_id' => $admin->id, + 'target_type' => User::class, + 'item_id' => $licenseSeat->license->id, + 'item_type' => License::class, + 'note' => 'oh hi there', + ]); + } +} diff --git a/tests/Feature/Reports/CustomReportTest.php b/tests/Feature/Reports/CustomReportTest.php index a1a269a4a..dd3199212 100644 --- a/tests/Feature/Reports/CustomReportTest.php +++ b/tests/Feature/Reports/CustomReportTest.php @@ -11,6 +11,7 @@ use PHPUnit\Framework\Assert; use Tests\Support\InteractsWithSettings; use Tests\TestCase; + class CustomReportTest extends TestCase { use InteractsWithSettings; diff --git a/tests/Unit/Helpers/HelperTest.php b/tests/Unit/Helpers/HelperTest.php new file mode 100644 index 000000000..0b5fba986 --- /dev/null +++ b/tests/Unit/Helpers/HelperTest.php @@ -0,0 +1,19 @@ +assertIsString(Helper::defaultChartColors(1000)); + } + + public function testDefaultChartColorsMethodHandlesNegativeNumbers() + { + $this->assertIsString(Helper::defaultChartColors(-1)); + } +}