Skip to content

Commit 0b7ea53

Browse files
committed
This is a squashed branch of all of the various commits that make up the new HasCustomFields trait.
This should allow us to add custom fields to just about anything we want to within Snipe-IT. Below are the commits that have been squashed together: Initial decoupling of custom field behavior from Assets for re-use Add new DB columns to Custom Fields and fieldsets for 'type' WIP: trying to figure out UI for custom fields for things other than Assets, find problematic places Real progress towards getting to where this stuff might actually work... Fix the table-name determining code for Custom Fields Getting it closer to where Assets at least work Rename the trait to it's new, even better name Solid progress on the new Trait! WIP: HasCustomFields, still working some stuff out Got some basics working; creating custom fields and stuff HasCustomFields now validates and saves Starting to yank the other boilerplate code as things start to work (!) Got the start of defaultValuesForCustomField() working More progress (squash me!) Add migrations for default_values_for_custom_fields table WIP: more towards hasCustomFields trait Progress cleaning up the PR, fixing FIXME's New, passing HasCustomFieldsTrait test! Fix date formatter helper for custom fields Fixed more FIXME's Got a chunk of Custom Fields for Users worked out, still needs cleanup Wiring up custom fields for users - still some big UI challenges tho Remove some code duplication Refactor out common code for 'custom fields view' partial Clean up this migration so it runs forwards and backwards OK Clean up the fieldset experience for custom fields for users Make tests pass, at least to start with. Still needs cleanup
1 parent c093b45 commit 0b7ea53

File tree

49 files changed

+1033
-382
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1033
-382
lines changed

app/Helpers/CustomFieldHelper.php

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
namespace App\Helpers;
4+
5+
use Illuminate\Support\Facades\Gate;
6+
7+
/*********************
8+
* These two helper methods are more designed for being re-used with the new HasCustomFields Trait
9+
*
10+
* The 'transform' method is designed for BlahTransformer things that need to return custom field values.
11+
*
12+
* The 'present' method is designed for when you're trying to generate fieldlists for use in Bootstrap tables
13+
* - typically the 'dataTableLayout' method
14+
*
15+
*********************/
16+
class CustomFieldHelper {
17+
18+
static function transform($fieldset, $item) {
19+
if ($fieldset && ($fieldset->fields->count() > 0)) {
20+
$fields_array = [];
21+
22+
foreach ($fieldset->fields as $field) {
23+
if ($field->isFieldDecryptable($item->{$field->db_column})) {
24+
$decrypted = Helper::gracefulDecrypt($field, $item->{$field->db_column});
25+
$value = (Gate::allows('assets.view.encrypted_custom_fields')) ? $decrypted : strtoupper(trans('admin/custom_fields/general.encrypted'));
26+
27+
if ($field->format == 'DATE'){
28+
if (Gate::allows('assets.view.encrypted_custom_fields')){
29+
$value = Helper::getFormattedDateObject($value, 'date', false);
30+
} else {
31+
$value = strtoupper(trans('admin/custom_fields/general.encrypted'));
32+
}
33+
}
34+
35+
$fields_array[$field->name] = [
36+
'field' => e($field->db_column),
37+
'value' => e($value),
38+
'field_format' => $field->format,
39+
'element' => $field->element,
40+
];
41+
42+
} else {
43+
$value = $item->{$field->db_column};
44+
45+
if (($field->format == 'DATE') && (!is_null($value)) && ($value!='')){
46+
$value = Helper::getFormattedDateObject($value, 'date', false);
47+
}
48+
49+
$fields_array[$field->name] = [
50+
'field' => e($field->db_column),
51+
'value' => e($value),
52+
'field_format' => $field->format,
53+
'element' => $field->element,
54+
];
55+
}
56+
57+
return $fields_array;
58+
}
59+
} else {
60+
return new \stdClass; // HACK to force generation of empty object instead of empty list
61+
}
62+
}
63+
64+
static function present($field) {
65+
return [
66+
'field' => 'custom_fields.'.$field->db_column,
67+
'searchable' => true,
68+
'sortable' => true,
69+
'switchable' => true,
70+
'title' => $field->name,
71+
'formatter'=> 'customFieldsFormatter',
72+
'escape' => true,
73+
'class' => ($field->field_encrypted == '1') ? 'css-padlock' : '',
74+
'visible' => ($field->show_in_listview == '1') ? true : false,
75+
];
76+
}
77+
}

app/Helpers/Helper.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,17 @@ public static function customFieldsetList()
653653
return $customfields;
654654
}
655655

656+
/**
657+
* Get all of the different types of custom fields there are
658+
* TODO - how to make this more general? Or more useful? or more dynamic?
659+
* idea - key of classname, *value* of trans? (thus having to make this a method, which is fine)
660+
*/
661+
static $itemtypes_having_custom_fields = [
662+
0 => \App\Models\Asset::class,
663+
1 => \App\Models\User::class,
664+
// 2 => \App\Models\Accessory::class
665+
];
666+
656667
/**
657668
* Get the list of custom field formats in an array to make a dropdown menu
658669
*

app/Http/Controllers/Api/AssetModelsController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public function index(Request $request) : JsonResponse | array
7070
'models.deleted_at',
7171
'models.updated_at',
7272
])
73-
->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues', 'adminuser')
73+
->with('category', 'depreciation', 'manufacturer', 'adminuser')
7474
->withCount('assets as assets_count');
7575

7676
if ($request->input('status')=='deleted') {

app/Http/Controllers/Api/AssetsController.php

Lines changed: 16 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public function index(Request $request, $action = null, $upcoming_status = null)
120120
$filter = json_decode($request->input('filter'), true);
121121
}
122122

123-
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
123+
$all_custom_fields = CustomField::where('type', Asset::class); //used as a 'cache' of custom fields throughout this page load
124124
foreach ($all_custom_fields as $field) {
125125
$allowed_columns[] = $field->db_column_name();
126126
}
@@ -610,48 +610,8 @@ public function store(StoreAssetRequest $request): JsonResponse
610610

611611
$asset = $request->handleImages($asset);
612612

613-
// Update custom fields in the database.
614-
$model = AssetModel::find($request->input('model_id'));
615-
616-
// Check that it's an object and not a collection
617-
// (Sometimes people send arrays here and they shouldn't
618-
if (($model) && ($model instanceof AssetModel) && ($model->fieldset)) {
619-
foreach ($model->fieldset->fields as $field) {
620-
621-
// Set the field value based on what was sent in the request
622-
$field_val = $request->input($field->db_column, null);
623-
624-
// If input value is null, use custom field's default value
625-
if ($field_val == null) {
626-
Log::debug('Field value for '.$field->db_column.' is null');
627-
$field_val = $field->defaultValue($request->get('model_id'));
628-
Log::debug('Use the default fieldset value of '.$field->defaultValue($request->get('model_id')));
629-
}
630-
631-
// if the field is set to encrypted, make sure we encrypt the value
632-
if ($field->field_encrypted == '1') {
633-
Log::debug('This model field is encrypted in this fieldset.');
613+
$asset->customFill($request, Auth::user(), true);
634614

635-
if (Gate::allows('assets.view.encrypted_custom_fields')) {
636-
637-
// If input value is null, use custom field's default value
638-
if (($field_val == null) && ($request->has('model_id') != '')) {
639-
$field_val = Crypt::encrypt($field->defaultValue($request->get('model_id')));
640-
} else {
641-
$field_val = Crypt::encrypt($request->input($field->db_column));
642-
}
643-
}
644-
}
645-
if ($field->element == 'checkbox') {
646-
if(is_array($field_val)) {
647-
$field_val = implode(',', $field_val);
648-
}
649-
}
650-
651-
652-
$asset->{$field->db_column} = $field_val;
653-
}
654-
}
655615

656616
if ($asset->save()) {
657617
if ($request->get('assigned_user')) {
@@ -709,33 +669,21 @@ public function update(UpdateAssetRequest $request, Asset $asset): JsonResponse
709669
$request->offsetSet('image', $request->offsetGet('image_source'));
710670
}
711671

672+
($request->filled('rtd_location_id')) ?
673+
$asset->location_id = $request->get('rtd_location_id') : null;
674+
675+
/**
676+
* this is here just legacy reasons. Api\AssetController
677+
* used image_source once to allow encoded image uploads.
678+
*/
679+
if ($request->has('image_source')) {
680+
$request->offsetSet('image', $request->offsetGet('image_source'));
681+
}
682+
712683
$asset = $request->handleImages($asset);
713-
$model = $asset->model;
714-
715-
// Update custom fields
716-
$problems_updating_encrypted_custom_fields = false;
717-
if (($model) && (isset($model->fieldset))) {
718-
foreach ($model->fieldset->fields as $field) {
719-
$field_val = $request->input($field->db_column, null);
720-
721-
if ($request->has($field->db_column)) {
722-
if ($field->element == 'checkbox') {
723-
if(is_array($field_val)) {
724-
$field_val = implode(',', $field_val);
725-
}
726-
}
727-
if ($field->field_encrypted == '1') {
728-
if (Gate::allows('assets.view.encrypted_custom_fields')) {
729-
$field_val = Crypt::encrypt($field_val);
730-
} else {
731-
$problems_updating_encrypted_custom_fields = true;
732-
continue;
733-
}
734-
}
735-
$asset->{$field->db_column} = $field_val;
736-
}
737-
}
738-
}
684+
685+
$problems_updating_encrypted_custom_fields = !$asset->customFill($request, Auth::user());
686+
739687
if ($asset->save()) {
740688
if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {
741689
$location = $target->location_id;

app/Http/Controllers/Api/CustomFieldsetsController.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class CustomFieldsetsController extends Controller
3333
public function index() : array
3434
{
3535
$this->authorize('index', CustomField::class);
36-
$fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get();
36+
$fieldsets = CustomFieldset::withCount('fields as fields_count')->get();
3737

3838
return (new CustomFieldsetsTransformer)->transformCustomFieldsets($fieldsets, $fieldsets->count());
3939
}
@@ -119,7 +119,7 @@ public function destroy($id) : JsonResponse
119119
$this->authorize('delete', CustomField::class);
120120
$fieldset = CustomFieldset::findOrFail($id);
121121

122-
$modelsCount = $fieldset->models->count();
122+
$modelsCount = $fieldset->customizables()->count();
123123
$fieldsCount = $fieldset->fields->count();
124124

125125
if (($modelsCount > 0) || ($fieldsCount > 0)) {

app/Http/Controllers/Api/UsersController.php

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
use App\Models\Accessory;
1717
use App\Models\Company;
1818
use App\Models\Consumable;
19+
use App\Models\Company;
20+
use App\Models\CustomField;
1921
use App\Models\License;
2022
use App\Models\User;
2123
use App\Notifications\CurrentInventory;
@@ -41,7 +43,7 @@ public function index(Request $request) : array
4143
{
4244
$this->authorize('view', User::class);
4345

44-
$users = User::select([
46+
$allowed_columns = [
4547
'users.activated',
4648
'users.address',
4749
'users.avatar',
@@ -79,7 +81,12 @@ public function index(Request $request) : array
7981
'users.autoassign_licenses',
8082
'users.website',
8183

82-
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy', 'managesUsers', 'managedLocations')
84+
];
85+
86+
foreach (CustomField::where('type', User::class)->get() as $field) {
87+
$allowed_columns[] = $field->db_column_name();
88+
}
89+
$users = User::select($allowed_columns)->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy', 'managesUsers', 'managedLocations')
8390
->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'managesUsers as manages_users_count', 'managedLocations as manages_locations_count');
8491

8592

@@ -393,7 +400,9 @@ public function store(SaveUserRequest $request) : JsonResponse
393400
}
394401

395402
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
396-
403+
404+
$user->customFill($request,Auth::user());
405+
397406
if ($user->save()) {
398407
if ($request->filled('groups')) {
399408
$user->groups()->sync($request->input('groups'));
@@ -466,6 +475,12 @@ public function update(SaveUserRequest $request, User $user): JsonResponse
466475
$user->password = bcrypt($request->input('password'));
467476
}
468477

478+
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
479+
480+
$user->customFill($request,Auth::user());
481+
482+
if ($user->save()) {
483+
469484
// We need to use has() instead of filled()
470485
// here because we need to overwrite permissions
471486
// if someone needs to null them out

app/Http/Controllers/AssetModelsController.php

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
use App\Http\Requests\ImageUploadRequest;
77
use App\Http\Requests\StoreAssetModelRequest;
88
use App\Models\Actionlog;
9+
use App\Models\Asset;
910
use App\Models\AssetModel;
1011
use App\Models\CustomField;
1112
use App\Models\SnipeModel;
1213
use App\Models\User;
14+
use App\Models\DefaultValuesForCustomFields;
1315
use Illuminate\Support\Facades\Auth;
1416
use Illuminate\Support\Facades\DB;
1517
use Illuminate\Support\Facades\Validator;
@@ -151,6 +153,8 @@ public function update(StoreAssetModelRequest $request, $modelId) : RedirectResp
151153
$model->notes = $request->input('notes');
152154
$model->requestable = $request->input('requestable', '0');
153155

156+
DefaultValuesForCustomFields::forPivot($model, Asset::class)->delete();
157+
154158
$model->fieldset_id = $request->input('fieldset_id');
155159

156160
if ($model->save()) {
@@ -454,7 +458,7 @@ private function shouldAddDefaultValues(array $input) : bool
454458
}
455459

456460
/**
457-
* Adds default values to a model (as long as they are truthy)
461+
* Adds default values to a model (as long as they are truthy) (does this mean I cannot set a default value of 0?)
458462
*
459463
* @param AssetModel $model
460464
* @param array $defaultValues
@@ -488,21 +492,12 @@ private function assignCustomFieldsDefaultValues(AssetModel|SnipeModel $model, a
488492
}
489493

490494
foreach ($defaultValues as $customFieldId => $defaultValue) {
491-
if(is_array($defaultValue)){
492-
$model->defaultValues()->attach($customFieldId, ['default_value' => implode(', ', $defaultValue)]);
493-
}elseif ($defaultValue) {
494-
$model->defaultValues()->attach($customFieldId, ['default_value' => $defaultValue]);
495+
if (is_array($defaultValue)) {
496+
$defaultValue = implode(', ', $defaultValue);
495497
}
498+
DefaultValuesForCustomFields::updateOrCreate(['custom_field_id' => $customFieldId, 'item_pivot_id' => $model->id], ['default_value' => $defaultValue]);
496499
}
497500
return true;
498501
}
499502

500-
/**
501-
* Removes all default values
502-
*
503-
*/
504-
private function removeCustomFieldsDefaultValues(AssetModel|SnipeModel $model): void
505-
{
506-
$model->defaultValues()->detach();
507-
}
508503
}

app/Http/Controllers/Assets/AssetsController.php

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -160,29 +160,7 @@ public function store(ImageUploadRequest $request) : RedirectResponse
160160
$asset = $request->handleImages($asset);
161161
}
162162

163-
// Update custom fields in the database.
164-
// Validation for these fields is handled through the AssetRequest form request
165-
$model = AssetModel::find($request->get('model_id'));
166-
167-
if (($model) && ($model->fieldset)) {
168-
foreach ($model->fieldset->fields as $field) {
169-
if ($field->field_encrypted == '1') {
170-
if (Gate::allows('assets.view.encrypted_custom_fields')) {
171-
if (is_array($request->input($field->db_column))) {
172-
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
173-
} else {
174-
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
175-
}
176-
}
177-
} else {
178-
if (is_array($request->input($field->db_column))) {
179-
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
180-
} else {
181-
$asset->{$field->db_column} = $request->input($field->db_column);
182-
}
183-
}
184-
}
185-
}
163+
$asset->customFill($request, Auth::user()); // Update custom fields in the database.
186164

187165
// Validate the asset before saving
188166
if ($asset->isValid() && $asset->save()) {

0 commit comments

Comments
 (0)