diff --git a/.env.dusk.local b/.env.dusk.example similarity index 94% rename from .env.dusk.local rename to .env.dusk.example index 33343ffc5..074f6fc3d 100644 --- a/.env.dusk.local +++ b/.env.dusk.example @@ -20,13 +20,13 @@ PUBLIC_FILESYSTEM_DISK=local_public # REQUIRED: DATABASE SETTINGS # -------------------------------------------- DB_CONNECTION=mysql -DB_HOST=localhost +DB_HOST=127.0.0.1 DB_PORT=3306 -DB_DATABASE=snipeit-local -DB_USERNAME=snipeit-local -DB_PASSWORD=snipeit-local +DB_DATABASE=null +DB_USERNAME=null +DB_PASSWORD=null DB_PREFIX=null -DB_DUMP_PATH='/Applications/MAMP/Library/bin' +#DB_DUMP_PATH= # -------------------------------------------- # OPTIONAL: SSL DATABASE SETTINGS diff --git a/.gitignore b/.gitignore index 37e9d3f68..078a7e4b8 100755 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ .couscous .DS_Store .env +.env.dusk.* +!.env.dusk.example .idea /bin/ /bootstrap/compiled.php diff --git a/TESTING.md b/TESTING.md index 662428975..8a430d498 100644 --- a/TESTING.md +++ b/TESTING.md @@ -43,23 +43,33 @@ you want to run. ## Browser Tests -The browser tests use [Dusk](https://laravel.com/docs/8.x/dusk) to run them. -When troubleshooting any problems, make sure that your `.env` file is configured -correctly to run the existing application. +Browser tests are run via [Laravel Dusk](https://laravel.com/docs/8.x/dusk) and require Google Chrome to be installed. + +Before attempting to run Dusk tests copy the example environment file for Dusk and update the values to match your environment: + +`cp .env.dusk.example .env.dusk.local` +> `local` refers to the value of `APP_ENV` in your `.env` so if you have it set to `dev` then the file should be named `.env.dusk.dev`. + +**Important**: Dusk tests cannot be run using an in-memory SQLite database. Additionally, the Dusk test suite uses the `DatabaseMigrations` trait which will leave the database in a fresh state after running. Therefore, it is recommended that you create a test database and point `DB_DATABASE` in `.env.dusk.local` to it. ### Test Setup -Your application needs to be configued and up and running in order for the browser +Your application needs to be configured and up and running in order for the browser tests to actually run. When running the tests locally, you can start the application using the following command: `php artisan serve` - -To run the test suite use the following command from another terminal tab or window: +Now you are ready to run the test suite. Use the following command from another terminal tab or window: `php artisan dusk` -To run individual test files, you can pass the path to the test that you want to run. +To run individual test files, you can pass the path to the test that you want to run: `php artisan dusk tests/Browser/LoginTest.php` + +If you get an error when attempting to run Dusk tests that says `Couldn't connect to server` run: + +`php artisan dusk:chrome-driver --detect` + +This command will install the specific ChromeDriver Dusk needs for your operating system and Chrome version. diff --git a/app/Console/Commands/LdapSync.php b/app/Console/Commands/LdapSync.php index c6f8dd379..c2d744693 100755 --- a/app/Console/Commands/LdapSync.php +++ b/app/Console/Commands/LdapSync.php @@ -44,12 +44,18 @@ class LdapSync extends Command */ public function handle() { + + // If LDAP enabled isn't set to 1 (ldap_enabled!=1) then we should cut this short immediately without going any further + if (Setting::getSettings()->ldap_enabled!='1') { + $this->error('LDAP is not enabled. Aborting. See Settings > LDAP to enable it.'); + exit(); + } + ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes ini_set('memory_limit', env('LDAP_MEM_LIM', '500M')); $ldap_result_username = Setting::getSettings()->ldap_username_field; $ldap_result_last_name = Setting::getSettings()->ldap_lname_field; $ldap_result_first_name = Setting::getSettings()->ldap_fname_field; - $ldap_result_active_flag = Setting::getSettings()->ldap_active_flag; $ldap_result_emp_num = Setting::getSettings()->ldap_emp_num; $ldap_result_email = Setting::getSettings()->ldap_email; @@ -303,17 +309,18 @@ class LdapSync extends Command $user->activated = 0; } */ $enabled_accounts = [ - '512', // 0x200 NORMAL_ACCOUNT - '544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD - '66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD - '66080', // 0x10220 NORMAL_ACCOUNT, PASSWD_NOTREQD, DONT_EXPIRE_PASSWORD - '262656', // 0x40200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED - '262688', // 0x40220 NORMAL_ACCOUNT, PASSWD_NOTREQD, SMARTCARD_REQUIRED - '328192', // 0x50200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD - '328224', // 0x50220 NORMAL_ACCOUNT, PASSWD_NOT_REQD, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD - '4194816',// 0x400200 NORMAL_ACCOUNT, DONT_REQ_PREAUTH + '512', // 0x200 NORMAL_ACCOUNT + '544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD + '66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD + '66080', // 0x10220 NORMAL_ACCOUNT, PASSWD_NOTREQD, DONT_EXPIRE_PASSWORD + '262656', // 0x40200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED + '262688', // 0x40220 NORMAL_ACCOUNT, PASSWD_NOTREQD, SMARTCARD_REQUIRED + '328192', // 0x50200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD + '328224', // 0x50220 NORMAL_ACCOUNT, PASSWD_NOT_REQD, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD + '4194816',// 0x400200 NORMAL_ACCOUNT, DONT_REQ_PREAUTH '4260352', // 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH '1049088', // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED + '1114624', // 0x110200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, NOT_DELEGATED, ]; $user->activated = (in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts)) ? 1 : 0; diff --git a/app/Console/Commands/RestoreFromBackup.php b/app/Console/Commands/RestoreFromBackup.php index c5e3a4c79..b1f175356 100644 --- a/app/Console/Commands/RestoreFromBackup.php +++ b/app/Console/Commands/RestoreFromBackup.php @@ -149,7 +149,7 @@ class RestoreFromBackup extends Command $boring_files[] = $raw_path; continue; } - if (@pathinfo($raw_path)['extension'] == 'sql') { + if (@pathinfo($raw_path, PATHINFO_EXTENSION) == 'sql') { \Log::debug("Found a sql file!"); $sqlfiles[] = $raw_path; $sqlfile_indices[] = $i; diff --git a/app/Http/Controllers/Accessories/AccessoriesController.php b/app/Http/Controllers/Accessories/AccessoriesController.php index d1af79adf..f0b54a949 100755 --- a/app/Http/Controllers/Accessories/AccessoriesController.php +++ b/app/Http/Controllers/Accessories/AccessoriesController.php @@ -63,6 +63,7 @@ class AccessoriesController extends Controller public function store(ImageUploadRequest $request) { $this->authorize(Accessory::class); + // create a new model instance $accessory = new Accessory(); @@ -82,7 +83,6 @@ class AccessoriesController extends Controller $accessory->supplier_id = request('supplier_id'); $accessory->notes = request('notes'); - $accessory = $request->handleImages($accessory); // Was the accessory created? @@ -127,45 +127,47 @@ class AccessoriesController extends Controller */ public function update(ImageUploadRequest $request, $accessoryId = null) { - if (is_null($accessory = Accessory::find($accessoryId))) { + if ($accessory = Accessory::withCount('users as users_count')->find($accessoryId)) { + + $this->authorize($accessory); + + $validator = Validator::make($request->all(), [ + "qty" => "required|numeric|min:$accessory->users_count" + ]); + + if ($validator->fails()) { + return redirect()->back() + ->withErrors($validator) + ->withInput(); + } + + + + // Update the accessory data + $accessory->name = request('name'); + $accessory->location_id = request('location_id'); + $accessory->min_amt = request('min_amt'); + $accessory->category_id = request('category_id'); + $accessory->company_id = Company::getIdForCurrentUser(request('company_id')); + $accessory->manufacturer_id = request('manufacturer_id'); + $accessory->order_number = request('order_number'); + $accessory->model_number = request('model_number'); + $accessory->purchase_date = request('purchase_date'); + $accessory->purchase_cost = Helper::ParseCurrency(request('purchase_cost')); + $accessory->qty = request('qty'); + $accessory->supplier_id = request('supplier_id'); + $accessory->notes = request('notes'); + + $accessory = $request->handleImages($accessory); + + // Was the accessory updated? + if ($accessory->save()) { + return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success')); + } + } else { return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); } - $min = $accessory->numCheckedOut(); - $validator = Validator::make($request->all(), [ - "qty" => "required|numeric|min:$min" - ]); - - if ($validator->fails()) { - return redirect()->back() - ->withErrors($validator) - ->withInput(); - } - - $this->authorize($accessory); - - // Update the accessory data - $accessory->name = request('name'); - $accessory->location_id = request('location_id'); - $accessory->min_amt = request('min_amt'); - $accessory->category_id = request('category_id'); - $accessory->company_id = Company::getIdForCurrentUser(request('company_id')); - $accessory->manufacturer_id = request('manufacturer_id'); - $accessory->order_number = request('order_number'); - $accessory->model_number = request('model_number'); - $accessory->purchase_date = request('purchase_date'); - $accessory->purchase_cost = Helper::ParseCurrency(request('purchase_cost')); - $accessory->qty = request('qty'); - $accessory->supplier_id = request('supplier_id'); - $accessory->notes = request('notes'); - - $accessory = $request->handleImages($accessory); - - // Was the accessory updated? - if ($accessory->save()) { - return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success')); - } - return redirect()->back()->withInput()->withErrors($accessory->getErrors()); } @@ -217,7 +219,7 @@ class AccessoriesController extends Controller */ public function show($accessoryID = null) { - $accessory = Accessory::find($accessoryID); + $accessory = Accessory::withCount('users as users_count')->find($accessoryID); $this->authorize('view', $accessory); if (isset($accessory->id)) { return view('accessories/view', compact('accessory')); diff --git a/app/Http/Controllers/Account/AcceptanceController.php b/app/Http/Controllers/Account/AcceptanceController.php index 59c8f8843..726e164ba 100644 --- a/app/Http/Controllers/Account/AcceptanceController.php +++ b/app/Http/Controllers/Account/AcceptanceController.php @@ -222,8 +222,8 @@ class AcceptanceController extends Controller 'item_model' => $display_model, 'item_serial' => $item->serial, 'eula' => $item->getEula(), - 'check_out_date' => Carbon::parse($acceptance->created_at)->format($branding_settings->date_display_format), - 'accepted_date' => Carbon::parse($acceptance->accepted_at)->format($branding_settings->date_display_format), + 'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'), + 'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d'), 'assigned_to' => $assigned_to, 'company_name' => $branding_settings->site_name, 'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null, @@ -273,7 +273,7 @@ class AcceptanceController extends Controller 'item_tag' => $item->asset_tag, 'item_model' => $display_model, 'item_serial' => $item->serial, - 'declined_date' => Carbon::parse($acceptance->accepted_at)->format($branding_settings->date_display_format), + 'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'), 'assigned_to' => $assigned_to, 'company_name' => $branding_settings->site_name, 'date_settings' => $branding_settings->date_display_format, diff --git a/app/Http/Controllers/Api/AccessoriesController.php b/app/Http/Controllers/Api/AccessoriesController.php index a894dc376..fd21ebaf3 100644 --- a/app/Http/Controllers/Api/AccessoriesController.php +++ b/app/Http/Controllers/Api/AccessoriesController.php @@ -41,10 +41,13 @@ class AccessoriesController extends Controller 'min_amt', 'company_id', 'notes', + 'users_count', + 'qty', ]; - $accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier'); + $accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier') + ->withCount('users as users_count'); if ($request->filled('search')) { $accessories = $accessories->TextSearch($request->input('search')); diff --git a/app/Http/Controllers/Api/CustomFieldsetsController.php b/app/Http/Controllers/Api/CustomFieldsetsController.php index 18da1b67c..27da7733c 100644 --- a/app/Http/Controllers/Api/CustomFieldsetsController.php +++ b/app/Http/Controllers/Api/CustomFieldsetsController.php @@ -33,7 +33,7 @@ class CustomFieldsetsController extends Controller */ public function index() { - $this->authorize('index', CustomFieldset::class); + $this->authorize('index', CustomField::class); $fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get(); return (new CustomFieldsetsTransformer)->transformCustomFieldsets($fieldsets, $fieldsets->count()); @@ -49,7 +49,7 @@ class CustomFieldsetsController extends Controller */ public function show($id) { - $this->authorize('view', CustomFieldset::class); + $this->authorize('view', CustomField::class); if ($fieldset = CustomFieldset::find($id)) { return (new CustomFieldsetsTransformer)->transformCustomFieldset($fieldset); } @@ -68,7 +68,7 @@ class CustomFieldsetsController extends Controller */ public function update(Request $request, $id) { - $this->authorize('update', CustomFieldset::class); + $this->authorize('update', CustomField::class); $fieldset = CustomFieldset::findOrFail($id); $fieldset->fill($request->all()); @@ -89,7 +89,7 @@ class CustomFieldsetsController extends Controller */ public function store(Request $request) { - $this->authorize('create', CustomFieldset::class); + $this->authorize('create', CustomField::class); $fieldset = new CustomFieldset; $fieldset->fill($request->all()); @@ -109,7 +109,7 @@ class CustomFieldsetsController extends Controller */ public function destroy($id) { - $this->authorize('delete', CustomFieldset::class); + $this->authorize('delete', CustomField::class); $fieldset = CustomFieldset::findOrFail($id); $modelsCount = $fieldset->models->count(); @@ -136,7 +136,7 @@ class CustomFieldsetsController extends Controller */ public function fields($id) { - $this->authorize('view', CustomFieldset::class); + $this->authorize('view', CustomField::class); $set = CustomFieldset::findOrFail($id); $fields = $set->fields; @@ -153,7 +153,7 @@ class CustomFieldsetsController extends Controller */ public function fieldsWithDefaultValues($fieldsetId, $modelId) { - $this->authorize('view', CustomFieldset::class); + $this->authorize('view', CustomField::class); $set = CustomFieldset::findOrFail($fieldsetId); diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index cca829c23..9a9135a38 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -542,9 +542,10 @@ class UsersController extends Controller if (empty($user->email)) { return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.inventorynotification.error'))); } + + $user->notify((new CurrentInventory($user))); - return response()->Helper::formatStandardApiResponse('success', null, trans('admin/users/message.inventorynotification.success')); - + return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.inventorynotification.success'))); } /** diff --git a/app/Http/Controllers/CustomFieldsetsController.php b/app/Http/Controllers/CustomFieldsetsController.php index c7c00a7bd..bee0f38e2 100644 --- a/app/Http/Controllers/CustomFieldsetsController.php +++ b/app/Http/Controllers/CustomFieldsetsController.php @@ -75,9 +75,9 @@ class CustomFieldsetsController extends Controller */ public function create() { - $this->authorize('create', CustomFieldset::class); + $this->authorize('create', CustomField::class); - return view('custom_fields.fieldsets.edit'); + return view('custom_fields.fieldsets.edit')->with('item', new CustomFieldset()); } /** @@ -91,7 +91,7 @@ class CustomFieldsetsController extends Controller */ public function store(Request $request) { - $this->authorize('create', CustomFieldset::class); + $this->authorize('create', CustomField::class); $cfset = new CustomFieldset([ 'name' => e($request->get('name')), @@ -110,31 +110,52 @@ class CustomFieldsetsController extends Controller } /** - * What the actual fuck, Brady? + * Presents edit form for fieldset * - * @todo Uhh, build this? - * @author [Brady Wetherington] [] + * @author [A. Gianotto] [] * @param int $id - * @since [v1.8] - * @return Fuckall + * @since [v6.0.14] + * @return Redirect + * @throws \Illuminate\Auth\Access\AuthorizationException */ public function edit($id) { - // + $this->authorize('create', CustomField::class); + + if ($fieldset = CustomFieldset::find($id)) { + return view('custom_fields.fieldsets.edit')->with('item', $fieldset); + } + + return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id])); + } /** - * GET IN THE SEA BRADY. + * Saves updated fieldset data * - * @todo Uhh, build this too? - * @author [Brady Wetherington] [] + * @author [A. Gianotto] [] * @param int $id - * @since [v1.8] - * @return Fuckall + * @since [v6.0.14] + * @return Redirect + * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function update($id) + public function update(Request $request, $id) { - // + $this->authorize('create', CustomField::class); + + if ($fieldset = CustomFieldset::find($id)) { + + $fieldset->name = $request->input('name'); + + if ($fieldset->save()) { + return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/general.fieldset_updated')); + } + + return redirect()->back()->withInput()->withErrors($fieldset->getErrors()); + + } + + return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id])); } /** @@ -148,7 +169,7 @@ class CustomFieldsetsController extends Controller */ public function destroy($id) { - $fieldset = CustomFieldset::find($id); + $fieldset = CustomField::find($id); $this->authorize('delete', $fieldset); @@ -175,7 +196,7 @@ class CustomFieldsetsController extends Controller */ public function associate(Request $request, $id) { - $set = CustomFieldset::find($id); + $set = CustomField::find($id); $this->authorize('update', $set); @@ -202,7 +223,7 @@ class CustomFieldsetsController extends Controller */ public function makeFieldRequired($fieldset_id, $field_id) { - $this->authorize('update', CustomFieldset::class); + $this->authorize('update', CustomField::class); $field = CustomField::findOrFail($field_id); $fieldset = CustomFieldset::findOrFail($fieldset_id); $fields[$field->id] = ['required' => 1]; @@ -220,7 +241,7 @@ class CustomFieldsetsController extends Controller */ public function makeFieldOptional($fieldset_id, $field_id) { - $this->authorize('update', CustomFieldset::class); + $this->authorize('update', CustomField::class); $field = CustomField::findOrFail($field_id); $fieldset = CustomFieldset::findOrFail($fieldset_id); $fields[$field->id] = ['required' => 0]; diff --git a/app/Http/Transformers/AccessoriesTransformer.php b/app/Http/Transformers/AccessoriesTransformer.php index 6f254b3b8..00c30f9ea 100644 --- a/app/Http/Transformers/AccessoriesTransformer.php +++ b/app/Http/Transformers/AccessoriesTransformer.php @@ -38,7 +38,8 @@ class AccessoriesTransformer 'purchase_cost' => Helper::formatCurrencyOutput($accessory->purchase_cost), 'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null, 'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null, - 'remaining_qty' => $accessory->numRemaining(), + 'remaining_qty' => (int) $accessory->numRemaining(), + 'users_count' => $accessory->users_count, 'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'), diff --git a/app/Models/Accessory.php b/app/Models/Accessory.php index 3f2004b04..d39d65338 100755 --- a/app/Models/Accessory.php +++ b/app/Models/Accessory.php @@ -327,20 +327,6 @@ class Accessory extends SnipeModel return null; } - /** - * Check how many items within an accessory are checked out - * - * @author [A. Gianotto] [] - * @since [v5.0] - * @return int - */ - public function numCheckedOut() - { - $checkedout = 0; - $checkedout = $this->users->count(); - - return $checkedout; - } /** * Check how many items of an accessory remain @@ -351,11 +337,11 @@ class Accessory extends SnipeModel */ public function numRemaining() { - $checkedout = $this->users->count(); + $checkedout = $this->users_count; $total = $this->qty; $remaining = $total - $checkedout; - return $remaining; + return (int) $remaining; } /** diff --git a/app/Models/SnipeSCIMConfig.php b/app/Models/SnipeSCIMConfig.php index 36a9ac855..77cbf01c1 100644 --- a/app/Models/SnipeSCIMConfig.php +++ b/app/Models/SnipeSCIMConfig.php @@ -12,94 +12,9 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig { public function getUserConfig() { - $config = parent::getUserConfig(); - // Much of this is copied verbatim from the library, then adjusted for our needs - $config['class'] = SCIMUser::class; - unset($config['mapping']['example:name:space']); - - $config['map_unmapped'] = false; // anything we don't explicitly map will _not_ show up. - - $core_namespace = 'urn:ietf:params:scim:schemas:core:2.0:User'; - $core = $core_namespace.':'; - $mappings =& $config['mapping'][$core_namespace]; //grab this entire key, we don't want to be repeating ourselves - - //username - *REQUIRED* - $config['validations'][$core.'userName'] = 'required'; - $mappings['userName'] = AttributeMapping::eloquent('username'); - - //human name - *FIRST NAME REQUIRED* - $config['validations'][$core.'name.givenName'] = 'required'; - $config['validations'][$core.'name.familyName'] = 'string'; //not required - - $mappings['name']['familyName'] = AttributeMapping::eloquent("last_name"); - $mappings['name']['givenName'] = AttributeMapping::eloquent("first_name"); - $mappings['name']['formatted'] = (new AttributeMapping())->ignoreWrite()->setRead( - function (&$object) { - return $object->getFullNameAttribute(); - } - ); - - // externalId support - $config['validations'][$core.'externalId'] = 'string|nullable'; // not required, but supported mostly just for Okta - // note that the mapping is *not* namespaced like the other $mappings - $config['mapping']['externalId'] = AttributeMapping::eloquent('scim_externalid'); - - $config['validations'][$core.'emails'] = 'nullable|array'; // emails are not required in Snipe-IT... - $config['validations'][$core.'emails.*.value'] = 'email'; // ...(had to remove the recommended 'required' here) - - $mappings['emails'] = [[ - "value" => AttributeMapping::eloquent("email"), - "display" => null, - "type" => AttributeMapping::constant("work")->ignoreWrite(), - "primary" => AttributeMapping::constant(true)->ignoreWrite() - ]]; - - //active - $config['validations'][$core.'active'] = 'boolean'; - - $mappings['active'] = AttributeMapping::eloquent('activated'); - - //phone - $config['validations'][$core.'phoneNumbers'] = 'nullable|array'; - $config['validations'][$core.'phoneNumbers.*.value'] = 'string'; // another one where want to say 'we don't _need_ a phone number, but if you have one it better have a value. - - $mappings['phoneNumbers'] = [[ - "value" => AttributeMapping::eloquent("phone"), - "display" => null, - "type" => AttributeMapping::constant("work")->ignoreWrite(), - "primary" => AttributeMapping::constant(true)->ignoreWrite() - ]]; - - //address - $config['validations'][$core.'addresses'] = 'nullable|array'; - $config['validations'][$core.'addresses.*.streetAddress'] = 'string'; - $config['validations'][$core.'addresses.*.locality'] = 'string'; - $config['validations'][$core.'addresses.*.region'] = 'nullable|string'; - $config['validations'][$core.'addresses.*.postalCode'] = 'nullable|string'; - $config['validations'][$core.'addresses.*.country'] = 'string'; - - $mappings['addresses'] = [[ - 'type' => AttributeMapping::constant("work")->ignoreWrite(), - 'formatted' => AttributeMapping::constant("n/a")->ignoreWrite(), // TODO - is this right? This doesn't look right. - 'streetAddress' => AttributeMapping::eloquent("address"), - 'locality' => AttributeMapping::eloquent("city"), - 'region' => AttributeMapping::eloquent("state"), - 'postalCode' => AttributeMapping::eloquent("zip"), - 'country' => AttributeMapping::eloquent("country"), - 'primary' => AttributeMapping::constant(true)->ignoreWrite() //this isn't in the example? - ]]; - - //title - $config['validations'][$core.'title'] = 'string'; - $mappings['title'] = AttributeMapping::eloquent('jobtitle'); - - //Preferred Language - $config['validations'][$core.'preferredLanguage'] = 'string'; - $mappings['preferredLanguage'] = AttributeMapping::eloquent('locale'); - - /* + /* more snipe-it attributes I'd like to check out (to map to 'enterprise' maybe?): - website - notes? @@ -108,66 +23,213 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig - company_id to "organization?" */ - $enterprise_namespace = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'; - $ent = $enterprise_namespace.':'; - // we remove the 'example' namespace and add the Enterprise one - $config['mapping']['schemas'] = AttributeMapping::constant( [$core_namespace, $enterprise_namespace] )->ignoreWrite(); + $user_prefix = 'urn:ietf:params:scim:schemas:core:2.0:User:'; + $enterprise_prefix = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:'; - $config['validations'][$ent.'employeeNumber'] = 'string'; - $config['validations'][$ent.'department'] = 'string'; - $config['validations'][$ent.'manager'] = 'nullable'; - $config['validations'][$ent.'manager.value'] = 'string'; + return [ - $config['mapping'][$enterprise_namespace] = [ - 'employeeNumber' => AttributeMapping::eloquent('employee_num'), - 'department' =>(new AttributeMapping())->setAdd( // FIXME parent? - function ($value, &$object) { - $department = Department::where("name", $value)->first(); - if ($department) { - $object->department_id = $department->id; - } - } - )->setReplace( - function ($value, &$object) { - $department = Department::where("name", $value)->first(); - if ($department) { - $object->department_id = $department->id; + // Set to 'null' to make use of auth.providers.users.model (App\User::class) + 'class' => SCIMUser::class, + + 'validations' => [ + $user_prefix . 'userName' => 'required', + $user_prefix . 'name.givenName' => 'required', + $user_prefix . 'name.familyName' => 'nullable|string', + $user_prefix . 'externalId' => 'nullable|string', + $user_prefix . 'emails' => 'nullable|array', + $user_prefix . 'emails.*.value' => 'nullable|email', + $user_prefix . 'active' => 'boolean', + $user_prefix . 'phoneNumbers' => 'nullable|array', + $user_prefix . 'phoneNumbers.*.value' => 'nullable|string', + $user_prefix . 'addresses' => 'nullable|array', + $user_prefix . 'addresses.*.streetAddress' => 'nullable|string', + $user_prefix . 'addresses.*.locality' => 'nullable|string', + $user_prefix . 'addresses.*.region' => 'nullable|string', + $user_prefix . 'addresses.*.postalCode' => 'nullable|string', + $user_prefix . 'addresses.*.country' => 'nullable|string', + $user_prefix . 'title' => 'nullable|string', + $user_prefix . 'preferredLanguage' => 'nullable|string', + + // Enterprise validations: + $enterprise_prefix . 'employeeNumber' => 'nullable|string', + $enterprise_prefix . 'department' => 'nullable|string', + $enterprise_prefix . 'manager' => 'nullable', + $enterprise_prefix . 'manager.value' => 'nullable|string' + ], + + 'singular' => 'User', + 'schema' => [Schema::SCHEMA_USER], + + //eager loading + 'withRelations' => [], + 'map_unmapped' => false, +// 'unmapped_namespace' => 'urn:ietf:params:scim:schemas:laravel:unmapped', + 'description' => 'User Account', + + // Map a SCIM attribute to an attribute of the object. + 'mapping' => [ + + 'id' => AttributeMapping::eloquent("id")->disableWrite(), + + 'externalId' => AttributeMapping::eloquent('scim_externalid'), // FIXME - I have a PR that changes a lot of this. + + 'meta' => [ + 'created' => AttributeMapping::eloquent("created_at")->disableWrite(), + 'lastModified' => AttributeMapping::eloquent("updated_at")->disableWrite(), + + 'location' => (new AttributeMapping())->setRead( + function ($object) { + return route( + 'scim.resource', + [ + 'resourceType' => 'Users', + 'resourceObject' => $object->id + ] + ); } - } - )->setRead( - function (&$object) { - return $object->department ? $object->department->name : null; - } - ), - 'manager' => [ - // FIXME - manager writes are disabled. This kinda works but it leaks errors all over the place. Not cool. - // '$ref' => (new AttributeMapping())->ignoreWrite()->ignoreRead(), - // 'displayName' => (new AttributeMapping())->ignoreWrite()->ignoreRead(), - // NOTE: you could probably do a 'plain' Eloquent mapping here, but we don't for future-proofing - 'value' => (new AttributeMapping())->setAdd( - function ($value, &$object) { - $manager = User::find($value); - if ($manager) { - $object->manager_id = $manager->id; + )->disableWrite(), + + 'resourceType' => AttributeMapping::constant("User") + ], + + 'schemas' => AttributeMapping::constant( + [ + 'urn:ietf:params:scim:schemas:core:2.0:User', + 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' + ] + )->ignoreWrite(), + + 'urn:ietf:params:scim:schemas:core:2.0:User' => [ + + 'userName' => AttributeMapping::eloquent("username"), + + 'name' => [ + 'formatted' => (new AttributeMapping())->ignoreWrite()->setRead( + function (&$object) { + return $object->getFullNameAttribute(); + } + ), + 'familyName' => AttributeMapping::eloquent("last_name"), + 'givenName' => AttributeMapping::eloquent("first_name"), + 'middleName' => null, + 'honorificPrefix' => null, + 'honorificSuffix' => null + ], + + 'displayName' => null, + 'nickName' => null, + 'profileUrl' => null, + 'title' => AttributeMapping::eloquent('jobtitle'), + 'userType' => null, + 'preferredLanguage' => AttributeMapping::eloquent('locale'), // Section 5.3.5 of [RFC7231] + 'locale' => null, // see RFC5646 + 'timezone' => null, // see RFC6557 + 'active' => AttributeMapping::eloquent('activated'), + + 'password' => AttributeMapping::eloquent('password')->disableRead(), + + // Multi-Valued Attributes + 'emails' => [[ + "value" => AttributeMapping::eloquent("email"), + "display" => null, + "type" => AttributeMapping::constant("work")->ignoreWrite(), + "primary" => AttributeMapping::constant(true)->ignoreWrite() + ]], + + 'phoneNumbers' => [[ + "value" => AttributeMapping::eloquent("phone"), + "display" => null, + "type" => AttributeMapping::constant("work")->ignoreWrite(), + "primary" => AttributeMapping::constant(true)->ignoreWrite() + ]], + + 'ims' => [[ + "value" => null, + "display" => null, + "type" => null, + "primary" => null + ]], // Instant messaging addresses for the User + + 'photos' => [[ + "value" => null, + "display" => null, + "type" => null, + "primary" => null + ]], + + 'addresses' => [[ + 'type' => AttributeMapping::constant("work")->ignoreWrite(), + 'formatted' => AttributeMapping::constant("n/a")->ignoreWrite(), // TODO - is this right? This doesn't look right. + 'streetAddress' => AttributeMapping::eloquent("address"), + 'locality' => AttributeMapping::eloquent("city"), + 'region' => AttributeMapping::eloquent("state"), + 'postalCode' => AttributeMapping::eloquent("zip"), + 'country' => AttributeMapping::eloquent("country"), + 'primary' => AttributeMapping::constant(true)->ignoreWrite() //this isn't in the example? + ]], + + 'groups' => [[ + 'value' => null, + '$ref' => null, + 'display' => null, + 'type' => null, + 'type' => null + ]], + + 'entitlements' => null, + 'roles' => null, + 'x509Certificates' => null + ], + + 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => [ + 'employeeNumber' => AttributeMapping::eloquent('employee_num'), + 'department' => (new AttributeMapping())->setAdd( // FIXME parent? + function ($value, &$object) { + $department = Department::where("name", $value)->first(); + if ($department) { + $object->department_id = $department->id; + } } - } )->setReplace( function ($value, &$object) { - $manager = User::find($value); - if ($manager) { - $object->manager_id = $manager->id; + $department = Department::where("name", $value)->first(); + if ($department) { + $object->department_id = $department->id; } } )->setRead( function (&$object) { - return $object->manager_id; - } + return $object->department ? $object->department->name : null; + } ), + 'manager' => [ + // FIXME - manager writes are disabled. This kinda works but it leaks errors all over the place. Not cool. + // '$ref' => (new AttributeMapping())->ignoreWrite()->ignoreRead(), + // 'displayName' => (new AttributeMapping())->ignoreWrite()->ignoreRead(), + // NOTE: you could probably do a 'plain' Eloquent mapping here, but we don't for future-proofing + 'value' => (new AttributeMapping())->setAdd( + function ($value, &$object) { + $manager = User::find($value); + if ($manager) { + $object->manager_id = $manager->id; + } + } + )->setReplace( + function ($value, &$object) { + $manager = User::find($value); + if ($manager) { + $object->manager_id = $manager->id; + } + } + )->setRead( + function (&$object) { + return $object->manager_id; + } + ), + ] + ] ] ]; - - return $config; } - } diff --git a/app/Models/User.php b/app/Models/User.php old mode 100755 new mode 100644 diff --git a/app/Presenters/AccessoryPresenter.php b/app/Presenters/AccessoryPresenter.php index 7d77acc8d..cc4f9badf 100644 --- a/app/Presenters/AccessoryPresenter.php +++ b/app/Presenters/AccessoryPresenter.php @@ -80,19 +80,25 @@ class AccessoryPresenter extends Presenter ], [ 'field' => 'qty', 'searchable' => false, - 'sortable' => false, - 'title' => trans('admin/accessories/general.total'), - ], [ - 'field' => 'min_qty', - 'searchable' => false, 'sortable' => true, - 'title' => trans('general.min_amt'), + 'title' => trans('admin/accessories/general.total'), ], [ 'field' => 'remaining_qty', 'searchable' => false, 'sortable' => false, 'visible' => false, 'title' => trans('admin/accessories/general.remaining'), + ],[ + 'field' => 'users_count', + 'searchable' => false, + 'sortable' => true, + 'visible' => true, + 'title' => trans('general.checked_out'), + ], [ + 'field' => 'min_qty', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.min_amt'), ], [ 'field' => 'purchase_date', 'searchable' => true, diff --git a/composer.json b/composer.json index 07dbf2063..06d856303 100644 --- a/composer.json +++ b/composer.json @@ -73,7 +73,7 @@ }, "require-dev": { "fakerphp/faker": "^1.16", - "laravel/dusk": "^6.19", + "laravel/dusk": "^6.25", "mockery/mockery": "^1.4", "phpunit/php-token-stream": "^3.1", "phpunit/phpunit": "^9.0", diff --git a/composer.lock b/composer.lock index bf8cb1f75..d23081089 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": "0c1f3848f8c9ede2b5f1513e4feaa7d7", + "content-hash": "4fed0ab76a34ef85a44568e470abab48", "packages": [ { "name": "alek13/slack", @@ -78,12 +78,12 @@ "source": { "type": "git", "url": "https://github.com/grokability/laravel-scim-server.git", - "reference": "2c7ecc450eee59234e059ec2e7724b2d8f3a8369" + "reference": "9e8dd2d3958d3c3c05d0a99fe6475361ad9e9419" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/grokability/laravel-scim-server/zipball/2c7ecc450eee59234e059ec2e7724b2d8f3a8369", - "reference": "2c7ecc450eee59234e059ec2e7724b2d8f3a8369", + "url": "https://api.github.com/repos/grokability/laravel-scim-server/zipball/9e8dd2d3958d3c3c05d0a99fe6475361ad9e9419", + "reference": "9e8dd2d3958d3c3c05d0a99fe6475361ad9e9419", "shasum": "" }, "require": { @@ -133,7 +133,7 @@ "support": { "source": "https://github.com/grokability/laravel-scim-server/tree/master" }, - "time": "2022-11-22T20:26:54+00:00" + "time": "2023-01-12T00:32:07+00:00" }, { "name": "asm89/stack-cors", @@ -11847,16 +11847,16 @@ }, { "name": "laravel/dusk", - "version": "v6.25.0", + "version": "v6.25.2", "source": { "type": "git", "url": "https://github.com/laravel/dusk.git", - "reference": "b4632b7493a187d31afc5c9ddec437c81b16421a" + "reference": "25a595ac3dc82089a91af10dd23b0d58fd3f6d0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/dusk/zipball/b4632b7493a187d31afc5c9ddec437c81b16421a", - "reference": "b4632b7493a187d31afc5c9ddec437c81b16421a", + "url": "https://api.github.com/repos/laravel/dusk/zipball/25a595ac3dc82089a91af10dd23b0d58fd3f6d0b", + "reference": "25a595ac3dc82089a91af10dd23b0d58fd3f6d0b", "shasum": "" }, "require": { @@ -11914,9 +11914,9 @@ ], "support": { "issues": "https://github.com/laravel/dusk/issues", - "source": "https://github.com/laravel/dusk/tree/v6.25.0" + "source": "https://github.com/laravel/dusk/tree/v6.25.2" }, - "time": "2022-07-11T11:38:43+00:00" + "time": "2022-09-29T09:37:07+00:00" }, { "name": "mockery/mockery", diff --git a/config/database.php b/config/database.php index 9d94454db..36440b212 100755 --- a/config/database.php +++ b/config/database.php @@ -102,6 +102,7 @@ return [ 'pgsql' => [ 'driver' => 'pgsql', 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', '5432'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), diff --git a/config/scim.php b/config/scim.php index d7d578efc..4e343cd3a 100644 --- a/config/scim.php +++ b/config/scim.php @@ -3,6 +3,6 @@ return [ "trace" => env("SCIM_TRACE",false), // below, if we ever get 'sure' that we can change this default to 'true' we should - "omit_main_schema_in_return" => env('SCIM_STANDARDS_COMPLIANCE', false), + "omit_main_schema_in_return" => env('SCIM_STANDARDS_COMPLIANCE', true), "publish_routes" => false, ]; diff --git a/database/migrations/2012_12_06_225929_migration_cartalyst_sentry_install_groups.php b/database/migrations/2012_12_06_225929_migration_cartalyst_sentry_install_groups.php index cd45847bc..55d731873 100644 --- a/database/migrations/2012_12_06_225929_migration_cartalyst_sentry_install_groups.php +++ b/database/migrations/2012_12_06_225929_migration_cartalyst_sentry_install_groups.php @@ -44,6 +44,8 @@ class MigrationCartalystSentryInstallGroups extends Migration */ public function down() { - Schema::drop('permission_groups'); + // See 2014_11_04_231416_update_group_field_for_reporting.php and 2019_06_12_184327_rename_groups_table.php + Schema::dropIfExists('permission_groups'); + Schema::dropIfExists('groups'); } } diff --git a/database/migrations/2013_11_17_054526_add_physical_to_assets.php b/database/migrations/2013_11_17_054526_add_physical_to_assets.php index 2ccc7c43d..28d111720 100755 --- a/database/migrations/2013_11_17_054526_add_physical_to_assets.php +++ b/database/migrations/2013_11_17_054526_add_physical_to_assets.php @@ -26,6 +26,6 @@ class AddPhysicalToAssets extends Migration */ public function down() { - $table->dropColumn('physical'); + // $table->dropColumn('physical'); } } diff --git a/database/migrations/2013_11_25_013244_recreate_licenses_table.php b/database/migrations/2013_11_25_013244_recreate_licenses_table.php index fb1845279..a22349dcd 100755 --- a/database/migrations/2013_11_25_013244_recreate_licenses_table.php +++ b/database/migrations/2013_11_25_013244_recreate_licenses_table.php @@ -37,7 +37,7 @@ class ReCreateLicensesTable extends Migration */ public function down() { - // - Schema::drop('licenses'); + // This was most likely handled in 2013_11_17_054359_drop_licenses_table.php + Schema::dropIfExists('licenses'); } } diff --git a/database/migrations/2013_11_25_031458_create_license_seats_table.php b/database/migrations/2013_11_25_031458_create_license_seats_table.php index 466ef0087..d023b8ebe 100755 --- a/database/migrations/2013_11_25_031458_create_license_seats_table.php +++ b/database/migrations/2013_11_25_031458_create_license_seats_table.php @@ -31,6 +31,6 @@ class CreateLicenseSeatsTable extends Migration */ public function down() { - // + Schema::dropIfExists('license_seats'); } } diff --git a/database/migrations/2013_11_25_104343_alter_warranty_column_on_assets.php b/database/migrations/2013_11_25_104343_alter_warranty_column_on_assets.php index be62374dc..d63a6ad9b 100755 --- a/database/migrations/2013_11_25_104343_alter_warranty_column_on_assets.php +++ b/database/migrations/2013_11_25_104343_alter_warranty_column_on_assets.php @@ -23,6 +23,8 @@ class AlterWarrantyColumnOnAssets extends Migration */ public function down() { - // + Schema::table('assets', function ($table) { + $table->renameColumn('warranty_months', 'warrantee_months'); + }); } } diff --git a/database/migrations/2013_12_10_084038_add_eol_on_models_table.php b/database/migrations/2013_12_10_084038_add_eol_on_models_table.php index d8d222dee..97e7b266e 100755 --- a/database/migrations/2013_12_10_084038_add_eol_on_models_table.php +++ b/database/migrations/2013_12_10_084038_add_eol_on_models_table.php @@ -24,7 +24,7 @@ class AddEolOnModelsTable extends Migration public function down() { Schema::table('models', function ($table) { - $table->dropColumn('old'); + $table->dropColumn('eol'); }); } } diff --git a/database/migrations/2015_07_29_230054_add_thread_id_to_asset_logs_table.php b/database/migrations/2015_07_29_230054_add_thread_id_to_asset_logs_table.php index eefc283e3..f14dc078c 100644 --- a/database/migrations/2015_07_29_230054_add_thread_id_to_asset_logs_table.php +++ b/database/migrations/2015_07_29_230054_add_thread_id_to_asset_logs_table.php @@ -105,10 +105,10 @@ */ public function down() { - Schema::table('asset_logs', function (Blueprint $table) { - $table->dropIndex('thread_id'); - $table->dropColumn('thread_id'); - }); + // Schema::table('asset_logs', function (Blueprint $table) { + // $table->dropIndex('thread_id'); + // $table->dropColumn('thread_id'); + // }); } /** diff --git a/database/migrations/2015_09_22_003413_migrate_mac_address.php b/database/migrations/2015_09_22_003413_migrate_mac_address.php index 516b5c2ec..3c4bf93e1 100644 --- a/database/migrations/2015_09_22_003413_migrate_mac_address.php +++ b/database/migrations/2015_09_22_003413_migrate_mac_address.php @@ -48,13 +48,19 @@ class MigrateMacAddress extends Migration */ public function down() { - // $f = \App\Models\CustomFieldset::where(['name' => 'Asset with MAC Address'])->first(); - $f->fields()->delete(); - $f->delete(); + + if ($f) { + $f->fields()->delete(); + $f->delete(); + } + Schema::table('models', function (Blueprint $table) { $table->renameColumn('deprecated_mac_address', 'show_mac_address'); }); - DB::statement('ALTER TABLE assets CHANGE _snipeit_mac_address mac_address varchar(255)'); + + if (Schema::hasColumn('assets', '_snipeit_mac_address')) { + DB::statement('ALTER TABLE assets CHANGE _snipeit_mac_address mac_address varchar(255)'); + } } } diff --git a/database/migrations/2016_08_23_145619_add_show_in_nav_to_status_labels.php b/database/migrations/2016_08_23_145619_add_show_in_nav_to_status_labels.php index c4e1a1817..5ea26bf4c 100644 --- a/database/migrations/2016_08_23_145619_add_show_in_nav_to_status_labels.php +++ b/database/migrations/2016_08_23_145619_add_show_in_nav_to_status_labels.php @@ -25,7 +25,7 @@ class AddShowInNavToStatusLabels extends Migration public function down() { Schema::table('status_labels', function (Blueprint $table) { - $table->dropColumn('show_in_nav', 'field_encrypted'); + $table->dropColumn('show_in_nav'); }); } } diff --git a/database/migrations/2017_01_25_063357_fix_utf8_custom_field_column_names.php b/database/migrations/2017_01_25_063357_fix_utf8_custom_field_column_names.php index 72e85698e..4725cccfe 100644 --- a/database/migrations/2017_01_25_063357_fix_utf8_custom_field_column_names.php +++ b/database/migrations/2017_01_25_063357_fix_utf8_custom_field_column_names.php @@ -4,6 +4,7 @@ use App\Models\CustomField; use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; +use Illuminate\Support\Str; /** * Fixes issue #2551 where columns got donked if the field name in non-ascii @@ -71,6 +72,25 @@ class FixUtf8CustomFieldColumnNames extends Migration */ public function down() { + // In the up method above, updateLegacyColumnName is called and custom fields in the assets table are prefixed + // with "_snipe_it_", suffixed with "_{id of the CustomField}", and stored in custom_fields.db_column. + // The following reverses those changes. + foreach (CustomField::all() as $field) { + $currentColumnName = $field->db_column; + + // "_snipeit_imei_1" becomes "_snipeit_imei" + $legacyColumnName = (string) Str::of($currentColumnName)->replaceMatches('/_(\d)+$/', ''); + + if (Schema::hasColumn(CustomField::$table_name, $currentColumnName)) { + Schema::table(CustomField::$table_name, function (Blueprint $table) use ($currentColumnName, $legacyColumnName) { + $table->renameColumn( + $currentColumnName, + $legacyColumnName + ); + }); + } + } + Schema::table('custom_fields', function ($table) { $table->dropColumn('db_column'); $table->dropColumn('help_text'); diff --git a/database/migrations/2017_03_10_210807_add_fields_to_manufacturer.php b/database/migrations/2017_03_10_210807_add_fields_to_manufacturer.php index 0d05a680c..05af7c831 100644 --- a/database/migrations/2017_03_10_210807_add_fields_to_manufacturer.php +++ b/database/migrations/2017_03_10_210807_add_fields_to_manufacturer.php @@ -28,7 +28,7 @@ class AddFieldsToManufacturer extends Migration */ public function down() { - Schema::table('settings', function ($table) { + Schema::table('manufacturers', function ($table) { $table->dropColumn('url'); $table->dropColumn('support_url'); $table->dropColumn('support_phone'); diff --git a/public/css/dist/skins/skin-contrast.css b/public/css/dist/skins/skin-contrast.css index b5d7fc4e9..50dfc577e 100644 --- a/public/css/dist/skins/skin-contrast.css +++ b/public/css/dist/skins/skin-contrast.css @@ -149,6 +149,13 @@ background-color: #000000; color: #fff; } +a.btn.btn-link.text-left { + color: #001F3F; + border: 1px solid #000; +} +a.btn.btn-link.text-left:hover { + color: #001F3F; +} a { color: #001F3F; } @@ -163,9 +170,6 @@ a.btn:hover { color: #fff; text-decoration: underline; } -a.btn:visited { - color: #fff; -} .text-primary { color: #000000; } diff --git a/public/css/dist/skins/skin-contrast.min.css b/public/css/dist/skins/skin-contrast.min.css index b5d7fc4e9..50dfc577e 100644 --- a/public/css/dist/skins/skin-contrast.min.css +++ b/public/css/dist/skins/skin-contrast.min.css @@ -149,6 +149,13 @@ background-color: #000000; color: #fff; } +a.btn.btn-link.text-left { + color: #001F3F; + border: 1px solid #000; +} +a.btn.btn-link.text-left:hover { + color: #001F3F; +} a { color: #001F3F; } @@ -163,9 +170,6 @@ a.btn:hover { color: #fff; text-decoration: underline; } -a.btn:visited { - color: #fff; -} .text-primary { color: #000000; } diff --git a/public/img/snipe-logo-bug.png b/public/img/snipe-logo-bug.png new file mode 100644 index 000000000..423a5b749 Binary files /dev/null and b/public/img/snipe-logo-bug.png differ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 719da79a9..b200128c7 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -16,7 +16,7 @@ "/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=76482123f6c70e866d6b971ba91de7bb", "/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=c0d21166315b7c2cdd4819fa4a5e4d1e", "/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=0a82a6ae6bb4e58fe62d162c4fb50397", - "/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=8e538625ebd4b8096e150d1aa483547b", + "/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=da6c7997d9de2f8329142399f0ce50da", "/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=44bf834f2110504a793dadec132a5898", "/css/dist/all.css": "/css/dist/all.css?id=ef030b613d45620b907cf0184a14e868", "/css/blue.png": "/css/blue.png?id=e83a6c29e04fe851f2122815b2e4b150", @@ -49,5 +49,5 @@ "/css/dist/skins/skin-purple-dark.min.css": "/css/dist/skins/skin-purple-dark.min.css?id=713b1205aa2d7c9db282f8cd5754c0e4", "/css/dist/skins/skin-orange.min.css": "/css/dist/skins/skin-orange.min.css?id=6f0563e726c2fe4fab4026daaa5bfdf2", "/css/dist/skins/skin-orange-dark.min.css": "/css/dist/skins/skin-orange-dark.min.css?id=f343f659ca1d45534d2c2c3cc30fb619", - "/css/dist/skins/skin-contrast.min.css": "/css/dist/skins/skin-contrast.min.css?id=8e538625ebd4b8096e150d1aa483547b" + "/css/dist/skins/skin-contrast.min.css": "/css/dist/skins/skin-contrast.min.css?id=da6c7997d9de2f8329142399f0ce50da" } diff --git a/resources/assets/less/skins/skin-contrast.less b/resources/assets/less/skins/skin-contrast.less index f66e76676..eba7001b0 100644 --- a/resources/assets/less/skins/skin-contrast.less +++ b/resources/assets/less/skins/skin-contrast.less @@ -84,7 +84,13 @@ color: #fff; } } - +a.btn.btn-link.text-left{ + color:@navy; + border: 1px solid #000; +} +a.btn.btn-link.text-left:hover{ + color:@navy; +} a { color: @navy; @@ -102,10 +108,6 @@ a.btn { &:hover { color: #fff; text-decoration: underline; - - } - &:visited { - color: #fff; } } diff --git a/resources/lang/en/admin/custom_fields/general.php b/resources/lang/en/admin/custom_fields/general.php index 92bf240a7..9dae380aa 100644 --- a/resources/lang/en/admin/custom_fields/general.php +++ b/resources/lang/en/admin/custom_fields/general.php @@ -27,6 +27,9 @@ return [ 'used_by_models' => 'Used By Models', 'order' => 'Order', 'create_fieldset' => 'New Fieldset', + 'update_fieldset' => 'Update Fieldset', + 'fieldset_does_not_exist' => 'Fieldset :id does not exist', + 'fieldset_updated' => 'Fieldset updated', 'create_fieldset_title' => 'Create a new fieldset', 'create_field' => 'New Custom Field', 'create_field_title' => 'Create a new custom field', diff --git a/resources/lang/en/admin/users/general.php b/resources/lang/en/admin/users/general.php index e845ed09b..96e283c15 100644 --- a/resources/lang/en/admin/users/general.php +++ b/resources/lang/en/admin/users/general.php @@ -46,4 +46,4 @@ return [ 'email_credentials' => 'Email credentials', 'email_credentials_text' => 'Email my credentials to the email address above', 'next_save_user' => 'Next: Save User', -]; +]; \ No newline at end of file diff --git a/resources/lang/en/general.php b/resources/lang/en/general.php index 43d8b3d9e..66872bd1b 100644 --- a/resources/lang/en/general.php +++ b/resources/lang/en/general.php @@ -390,7 +390,8 @@ return [ 'start_date' => 'Start Date', 'end_date' => 'End Date', 'alt_uploaded_image_thumbnail' => 'Uploaded thumbnail', - 'placeholder_kit' => 'Select a kit' + 'placeholder_kit' => 'Select a kit', + 'file_not_found' => 'File not found', diff --git a/resources/views/accessories/edit.blade.php b/resources/views/accessories/edit.blade.php old mode 100755 new mode 100644 diff --git a/resources/views/accessories/view.blade.php b/resources/views/accessories/view.blade.php index 82d7c8201..fd3172220 100644 --- a/resources/views/accessories/view.blade.php +++ b/resources/views/accessories/view.blade.php @@ -290,7 +290,7 @@ @if ($accessory->company)
- {{ trans('general.company')}} + {{ trans('general.company')}}
{{ $accessory->company->name }} @@ -302,7 +302,7 @@ @if ($accessory->category)
- {{ trans('general.category')}} + {{ trans('general.category')}}
{{ $accessory->category->name }} @@ -327,13 +327,22 @@
- Number remaining + {{ trans('admin/accessories/general.remaining') }}
{{ $accessory->numRemaining() }}
+
+
+ {{ trans('general.checked_out') }} +
+
+ {{ $accessory->users_count }} +
+
+ @can('checkout', \App\Models\Accessory::class) diff --git a/resources/views/consumables/checkout.blade.php b/resources/views/consumables/checkout.blade.php index f768f7848..85cec320e 100644 --- a/resources/views/consumables/checkout.blade.php +++ b/resources/views/consumables/checkout.blade.php @@ -60,7 +60,7 @@ @if ($snipeSettings->slack_endpoint!='') - A slack message will be sent + {{ trans('general.slack_msg_note') }} @endif
diff --git a/resources/views/custom_fields/fieldsets/edit.blade.php b/resources/views/custom_fields/fieldsets/edit.blade.php index 04885891e..7a35ca146 100644 --- a/resources/views/custom_fields/fieldsets/edit.blade.php +++ b/resources/views/custom_fields/fieldsets/edit.blade.php @@ -1,49 +1,17 @@ -@extends('layouts.default') +@extends('layouts/edit-form', [ + 'createText' => trans('admin/custom_fields/general.create_fieldset') , + 'updateText' => trans('admin/custom_fields/general.update_fieldset'), + 'helpText' => trans('admin/custom_fields/general.about_fieldsets_text'), + 'helpPosition' => 'right', + 'formAction' => (isset($item->id)) ? route('fieldsets.update', ['fieldset' => $item->id]) : route('fieldsets.store'), +]) -{{-- Page title --}} -@section('title') - {{ trans('admin/custom_fields/general.create_fieldset') }} -@parent -@stop - -@section('header_right') - - {{ trans('general.back') }} -@stop - - -{{-- Page content --}} @section('content') -
-
- - {{ Form::open(['route' => 'fieldsets.store', 'class'=>'form-horizontal']) }} - -
-
- - -
- -
- - {!! $errors->first('name', '') !!} -
-
- -
- - -
- {{ Form::close() }} -
-
-

{{ trans('admin/custom_fields/general.about_fieldsets_title') }}

-

{{ trans('admin/custom_fields/general.about_fieldsets_text') }}

-
-
+ @parent @stop + +@section('inputFields') +@include ('partials.forms.edit.name', ['translated_name' => trans('general.name')]) +@stop + + diff --git a/resources/views/custom_fields/index.blade.php b/resources/views/custom_fields/index.blade.php index 08c43cdd7..d2435d7db 100644 --- a/resources/views/custom_fields/index.blade.php +++ b/resources/views/custom_fields/index.blade.php @@ -69,8 +69,18 @@ @endforeach + + + + @can('update', $fieldset) + + + {{ trans('button.edit') }} + + @endcan + @can('delete', $fieldset) - {{ Form::open(['route' => array('fieldsets.destroy', $fieldset->id), 'method' => 'delete']) }} + {{ Form::open(['route' => array('fieldsets.destroy', $fieldset->id), 'method' => 'delete','style' => 'display:inline-block']) }} @if($fieldset->models->count() > 0) @else @@ -78,6 +88,7 @@ @endif {{ Form::close() }} @endcan + @endforeach diff --git a/resources/views/layouts/default.blade.php b/resources/views/layouts/default.blade.php index daf427548..ceabe1355 100644 --- a/resources/views/layouts/default.blade.php +++ b/resources/views/layouts/default.blade.php @@ -15,8 +15,8 @@ - - + + diff --git a/resources/views/partials/bootstrap-table.blade.php b/resources/views/partials/bootstrap-table.blade.php index 5a6e9dd46..d3a0fddc1 100644 --- a/resources/views/partials/bootstrap-table.blade.php +++ b/resources/views/partials/bootstrap-table.blade.php @@ -327,8 +327,8 @@ item_icon = ''; } - // display the username if it's checked out to a user - if (value.username) { + // display the username if it's checked out to a user, but don't do it if the username's there already + if (value.username && !value.name.match('\\(') && !value.name.match('\\)')) { value.name = value.name + ' (' + value.username + ')'; } diff --git a/resources/views/users/view.blade.php b/resources/views/users/view.blade.php index 5d959589e..3fb3b9a58 100755 --- a/resources/views/users/view.blade.php +++ b/resources/views/users/view.blade.php @@ -869,16 +869,19 @@ @if ($file->filename) - @if ( Helper::checkUploadIsImage($file->get_src('users'))) + @if ((Storage::exists('private_uploads/users/'.$file->filename)) && ( Helper::checkUploadIsImage($file->get_src('users')))) + @else + + {{ trans('general.file_not_found') }} @endif @endif {{ $file->filename }} - - {{ Helper::formatFilesizeUnits(Storage::size('private_uploads/users/'.$file->filename)) }} + + {{ (Storage::exists('private_uploads/users/'.$file->filename)) ? Helper::formatFilesizeUnits(Storage::size('private_uploads/users/'.$file->filename)) : '' }} @@ -888,10 +891,12 @@ @if ($file->filename) - - - {{ trans('general.download') }} - + @if ((Storage::exists('private_uploads/users/'.$file->filename)) && ( Helper::checkUploadIsImage($file->get_src('users')))) + + + {{ trans('general.download') }} + + @endif @endif {{ $file->created_at }} @@ -949,7 +954,8 @@ @endif {{ trans('admin/hardware/table.serial') }} {{ trans('general.item') }} - {{ trans('general.target') }} + {{ trans('general.notes') }} + {{ trans('general.target') }} diff --git a/tests/Browser/LoginTest.php b/tests/Browser/LoginTest.php index 5ed055cda..18f5172f1 100644 --- a/tests/Browser/LoginTest.php +++ b/tests/Browser/LoginTest.php @@ -2,8 +2,8 @@ namespace Tests\Browser; +use App\Models\Setting; use App\Models\User; -use Illuminate\Foundation\Testing\DatabaseMigrations; use Laravel\Dusk\Browser; use Tests\DuskTestCase; @@ -26,6 +26,9 @@ class LoginTest extends DuskTestCase $user->permissions = '{"superuser": 1}'; $user->save(); + + Setting::factory()->create(); + $this->browse(function (Browser $browser) { $browser->visitRoute('login') ->assertSee(trans('auth/general.login_prompt')); @@ -37,10 +40,7 @@ class LoginTest extends DuskTestCase ->type('password', 'password') ->press(trans('auth/general.login')) ->assertPathIs('/'); - $browser->screenshot('dashboard'); + $browser->screenshot('dashboard'); }); - - // Delete the user afterwards - $user->delete(); } } diff --git a/tests/DuskTestCase.php b/tests/DuskTestCase.php index ac750d9ef..e591a7c77 100644 --- a/tests/DuskTestCase.php +++ b/tests/DuskTestCase.php @@ -5,11 +5,13 @@ namespace Tests; use Facebook\WebDriver\Chrome\ChromeOptions; use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver; +use Illuminate\Foundation\Testing\DatabaseMigrations; use Laravel\Dusk\TestCase as BaseTestCase; abstract class DuskTestCase extends BaseTestCase { use CreatesApplication; + use DatabaseMigrations; /** * Prepare for Dusk test execution.