From 8902145288ae8b1428e43771b25869354018a924 Mon Sep 17 00:00:00 2001 From: Brady Wetherington Date: Tue, 29 Jun 2021 13:43:38 -0700 Subject: [PATCH] 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 --- app/Helpers/Helper.php | 11 ++ .../Controllers/Api/AssetModelsController.php | 2 +- app/Http/Controllers/Api/AssetsController.php | 54 +------ .../Api/CustomFieldsetsController.php | 4 +- .../Controllers/AssetModelsController.php | 21 +-- .../Controllers/Assets/AssetsController.php | 24 +-- .../Controllers/CustomFieldsController.php | 20 ++- .../Controllers/CustomFieldsetsController.php | 3 + .../CustomFieldSetDefaultValuesForModel.php | 4 +- app/Http/Livewire/Importer.php | 2 +- app/Http/Middleware/TrustProxies.php | 2 +- app/Http/Requests/CustomFieldRequest.php | 2 + .../CustomFieldsetsTransformer.php | 17 ++- app/Importer/AssetImporter.php | 7 +- app/Importer/Importer.php | 2 +- app/Models/Asset.php | 52 ++----- app/Models/AssetModel.php | 13 +- app/Models/CustomField.php | 72 ++++----- app/Models/CustomFieldset.php | 17 ++- app/Models/DefaultValuesForCustomFields.php | 34 +++++ app/Models/Traits/HasCustomFields.php | 138 ++++++++++++++++++ app/Presenters/AssetPresenter.php | 14 +- app/Providers/AuthServiceProvider.php | 2 +- config/app.php | 2 +- config/trustedproxy.php | 10 +- database/factories/AssetFactory.php | 11 ++ database/factories/AssetModelFactory.php | 10 ++ database/factories/CustomFieldFactory.php | 22 ++- database/factories/CustomFieldsetFactory.php | 13 ++ ...357_fix_utf8_custom_field_column_names.php | 8 +- ...06_29_212602_add_type_to_custom_fields.php | 38 +++++ ...29_212612_add_type_to_custom_fieldsets.php | 38 +++++ ...alize_default_values_for_custom_fields.php | 28 ++++ ...mn_to_default_values_for_custom_fields.php | 34 +++++ .../views/custom_fields/fields/edit.blade.php | 2 + .../custom_fields/fieldsets/edit.blade.php | 1 + resources/views/custom_fields/index.blade.php | 31 +++- tests/Unit/HasCustomFieldsTraitTest.php | 79 ++++++++++ 38 files changed, 623 insertions(+), 221 deletions(-) create mode 100644 app/Models/DefaultValuesForCustomFields.php create mode 100644 app/Models/Traits/HasCustomFields.php create mode 100644 database/migrations/2021_06_29_212602_add_type_to_custom_fields.php create mode 100644 database/migrations/2021_06_29_212612_add_type_to_custom_fieldsets.php create mode 100644 database/migrations/2023_08_10_121812_generalize_default_values_for_custom_fields.php create mode 100644 database/migrations/2023_08_14_164625_add_type_column_to_default_values_for_custom_fields.php create mode 100644 tests/Unit/HasCustomFieldsTraitTest.php diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php index 680c7acfd6..6808fbb04a 100644 --- a/app/Helpers/Helper.php +++ b/app/Helpers/Helper.php @@ -573,6 +573,17 @@ class Helper return $customfields; } + /** + * Get all of the different types of custom fields there are + * TODO - how to make this more general? Or more useful? or more dynamic? + * idea - key of classname, *value* of trans? (thus having to make this a method, which is fine) + */ + static $itemtypes_having_custom_fields = [ + 0 => \App\Models\Asset::class, + 1 => \App\Models\User::class, + 2 => \App\Models\Accessory::class + ]; + /** * Get the list of custom field formats in an array to make a dropdown menu * diff --git a/app/Http/Controllers/Api/AssetModelsController.php b/app/Http/Controllers/Api/AssetModelsController.php index cf13d24fc4..24067d72b4 100644 --- a/app/Http/Controllers/Api/AssetModelsController.php +++ b/app/Http/Controllers/Api/AssetModelsController.php @@ -63,7 +63,7 @@ class AssetModelsController extends Controller 'models.deleted_at', 'models.updated_at', ]) - ->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues') + ->with('category', 'depreciation', 'manufacturer') ->withCount('assets as assets_count'); if ($request->input('status')=='deleted') { diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index e8f37d8574..b88325977b 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -110,7 +110,7 @@ class AssetsController extends Controller $filter = json_decode($request->input('filter'), true); } - $all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load + $all_custom_fields = CustomField::where('type', Asset::class); //used as a 'cache' of custom fields throughout this page load foreach ($all_custom_fields as $field) { $allowed_columns[] = $field->db_column_name(); } @@ -573,42 +573,8 @@ class AssetsController extends Controller $asset = $request->handleImages($asset); - // Update custom fields in the database. - // Validation for these fields is handled through the AssetRequest form request - $model = AssetModel::find($request->get('model_id')); + $asset->customFill($request, Auth::user(), true); - if (($model) && ($model->fieldset)) { - foreach ($model->fieldset->fields as $field) { - - // Set the field value based on what was sent in the request - $field_val = $request->input($field->db_column, null); - - // If input value is null, use custom field's default value - if ($field_val == null) { - \Log::debug('Field value for '.$field->db_column.' is null'); - $field_val = $field->defaultValue($request->get('model_id')); - \Log::debug('Use the default fieldset value of '.$field->defaultValue($request->get('model_id'))); - } - - // if the field is set to encrypted, make sure we encrypt the value - if ($field->field_encrypted == '1') { - \Log::debug('This model field is encrypted in this fieldset.'); - - if (Gate::allows('admin')) { - - // If input value is null, use custom field's default value - if (($field_val == null) && ($request->has('model_id') != '')) { - $field_val = \Crypt::encrypt($field->defaultValue($request->get('model_id'))); - } else { - $field_val = \Crypt::encrypt($request->input($field->db_column)); - } - } - } - - - $asset->{$field->db_column} = $field_val; - } - } if ($asset->save()) { if ($request->get('assigned_user')) { @@ -668,21 +634,7 @@ class AssetsController extends Controller $asset = $request->handleImages($asset); - // Update custom fields - if (($model = AssetModel::find($asset->model_id)) && (isset($model->fieldset))) { - foreach ($model->fieldset->fields as $field) { - if ($request->has($field->db_column)) { - if ($field->field_encrypted == '1') { - if (Gate::allows('admin')) { - $asset->{$field->db_column} = \Crypt::encrypt($request->input($field->db_column)); - } - } else { - $asset->{$field->db_column} = $request->input($field->db_column); - } - } - } - } - + $asset->customFill($request,Auth::user()); if ($asset->save()) { if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) { diff --git a/app/Http/Controllers/Api/CustomFieldsetsController.php b/app/Http/Controllers/Api/CustomFieldsetsController.php index df0fc940cf..0c8ed734a7 100644 --- a/app/Http/Controllers/Api/CustomFieldsetsController.php +++ b/app/Http/Controllers/Api/CustomFieldsetsController.php @@ -35,7 +35,7 @@ class CustomFieldsetsController extends Controller public function index() { $this->authorize('index', CustomField::class); - $fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get(); + $fieldsets = CustomFieldset::withCount('fields as fields_count')->get(); return (new CustomFieldsetsTransformer)->transformCustomFieldsets($fieldsets, $fieldsets->count()); } @@ -125,7 +125,7 @@ class CustomFieldsetsController extends Controller $this->authorize('delete', CustomField::class); $fieldset = CustomFieldset::findOrFail($id); - $modelsCount = $fieldset->models->count(); + $modelsCount = $fieldset->customizables()->count(); $fieldsCount = $fieldset->fields->count(); if (($modelsCount > 0) || ($fieldsCount > 0)) { diff --git a/app/Http/Controllers/AssetModelsController.php b/app/Http/Controllers/AssetModelsController.php index dbefb2e7b7..736dfc2708 100755 --- a/app/Http/Controllers/AssetModelsController.php +++ b/app/Http/Controllers/AssetModelsController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers; use App\Helpers\Helper; use App\Http\Requests\ImageUploadRequest; use App\Models\AssetModel; +use App\Models\DefaultValuesForCustomFields; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Input; use Illuminate\Support\Facades\View; @@ -158,7 +159,7 @@ class AssetModelsController extends Controller $model->notes = $request->input('notes'); $model->requestable = $request->input('requestable', '0'); - $this->removeCustomFieldsDefaultValues($model); + DefaultValuesForCustomFields::forPivot($model, Asset::class)->delete(); if ($request->input('fieldset_id') == '') { $model->fieldset_id = null; @@ -448,7 +449,7 @@ class AssetModelsController extends Controller } /** - * Adds default values to a model (as long as they are truthy) + * Adds default values to a model (as long as they are truthy) (does this mean I cannot set a default value of 0?) * * @param AssetModel $model * @param array $defaultValues @@ -483,22 +484,12 @@ class AssetModelsController extends Controller } foreach ($defaultValues as $customFieldId => $defaultValue) { - if(is_array($defaultValue)){ - $model->defaultValues()->attach($customFieldId, ['default_value' => implode(', ', $defaultValue)]); - }elseif ($defaultValue) { - $model->defaultValues()->attach($customFieldId, ['default_value' => $defaultValue]); + if(is_array($defaultValue)) { + $defaultValue = implode(', ', $defaultValue); } + DefaultValuesForCustomFields::updateOrCreate(['custom_field_id' => $customFieldId,'item_pivot_id' => $model->id], ['default_value' => $defaultValue]); } return true; } - /** - * Removes all default values - * - * @return void - */ - private function removeCustomFieldsDefaultValues(AssetModel $model) - { - $model->defaultValues()->detach(); - } } diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index e699779a34..1aa41b6f31 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -164,29 +164,7 @@ class AssetsController extends Controller $asset = $request->handleImages($asset); } - // Update custom fields in the database. - // Validation for these fields is handled through the AssetRequest form request - $model = AssetModel::find($request->get('model_id')); - - if (($model) && ($model->fieldset)) { - foreach ($model->fieldset->fields as $field) { - if ($field->field_encrypted == '1') { - if (Gate::allows('admin')) { - if (is_array($request->input($field->db_column))) { - $asset->{$field->db_column} = \Crypt::encrypt(implode(', ', $request->input($field->db_column))); - } else { - $asset->{$field->db_column} = \Crypt::encrypt($request->input($field->db_column)); - } - } - } else { - if (is_array($request->input($field->db_column))) { - $asset->{$field->db_column} = implode(', ', $request->input($field->db_column)); - } else { - $asset->{$field->db_column} = $request->input($field->db_column); - } - } - } - } + $asset->customFill($request, Auth::user()); // Update custom fields in the database. // Validate the asset before saving if ($asset->isValid() && $asset->save()) { diff --git a/app/Http/Controllers/CustomFieldsController.php b/app/Http/Controllers/CustomFieldsController.php index c9579ae7ef..61eba6b7f9 100644 --- a/app/Http/Controllers/CustomFieldsController.php +++ b/app/Http/Controllers/CustomFieldsController.php @@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Http\Request; use Redirect; + /** * This controller handles all actions related to Custom Asset Fields for * the Snipe-IT Asset Management application. @@ -21,6 +22,7 @@ use Redirect; */ class CustomFieldsController extends Controller { + /** * Returns a view with a listing of custom fields. * @@ -29,12 +31,12 @@ class CustomFieldsController extends Controller * @return \Illuminate\Support\Facades\View * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function index() + public function index(Request $request) { $this->authorize('view', CustomField::class); - $fieldsets = CustomFieldset::with('fields', 'models')->get(); - $fields = CustomField::with('fieldset')->get(); + $fieldsets = CustomFieldset::with('fields')->where("type", Helper::$itemtypes_having_custom_fields[$request->get('tab',0)])->get(); //cannot eager-load 'customizable' because it's not a relation + $fields = CustomField::with('fieldset')->where("type", Helper::$itemtypes_having_custom_fields[$request->get('tab',0)])->get(); return view('custom_fields.index')->with('custom_fieldsets', $fieldsets)->with('custom_fields', $fields); } @@ -110,8 +112,10 @@ 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), - "user_id" => Auth::id() + "user_id" => Auth::id(), ]); + // not mass-assignable; must be manual + $field->type = Helper::$itemtypes_having_custom_fields[$request->get('tab')]; if ($request->filled('custom_format')) { @@ -131,7 +135,7 @@ class CustomFieldsController extends Controller } - return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/message.field.create.success')); + return redirect()->route('fields.index',['tab' => $request->get('tab',0)])->with('success', trans('admin/custom_fields/message.field.create.success')); } return redirect()->back()->with('selected_fieldsets', $request->input('associate_fieldsets'))->withInput() @@ -188,8 +192,8 @@ class CustomFieldsController extends Controller return redirect()->back()->withErrors(['message' => 'Field is in-use']); } $field->delete(); - return redirect()->route("fields.index") - ->with("success", trans('admin/custom_fields/message.field.delete.success')); + return redirect()->route('fields.index',['tab' => Request::query('tab',0)]) + ->with('success', trans('admin/custom_fields/message.field.delete.success')); } return redirect()->back()->withErrors(['message' => 'Field does not exist']); @@ -289,7 +293,7 @@ class CustomFieldsController extends Controller $field->fieldset()->sync([]); } - return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/message.field.update.success')); + return redirect()->route('fields.index',['tab' => $request->get('tab',0)])->with('success', trans('admin/custom_fields/message.field.update.success')); } return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.field.update.error')); diff --git a/app/Http/Controllers/CustomFieldsetsController.php b/app/Http/Controllers/CustomFieldsetsController.php index abf7c1d18e..a1ac7a5f5a 100644 --- a/app/Http/Controllers/CustomFieldsetsController.php +++ b/app/Http/Controllers/CustomFieldsetsController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Helpers\Helper; use App\Models\AssetModel; use App\Models\CustomField; use App\Models\CustomFieldset; @@ -96,6 +97,8 @@ class CustomFieldsetsController extends Controller $fieldset = new CustomFieldset([ 'name' => $request->get('name'), 'user_id' => Auth::user()->id, + 'type' => Helper::$itemtypes_having_custom_fields[$request->get('tab')] +// 'sub' => ]); $validator = Validator::make($request->all(), $fieldset->rules); diff --git a/app/Http/Livewire/CustomFieldSetDefaultValuesForModel.php b/app/Http/Livewire/CustomFieldSetDefaultValuesForModel.php index e1487d2151..1c6a30cb31 100644 --- a/app/Http/Livewire/CustomFieldSetDefaultValuesForModel.php +++ b/app/Http/Livewire/CustomFieldSetDefaultValuesForModel.php @@ -2,6 +2,8 @@ namespace App\Http\Livewire; +use App\Models\Asset; +use App\Models\DefaultValuesForCustomFields; use Livewire\Component; use App\Models\CustomFieldset; @@ -30,7 +32,7 @@ class CustomFieldSetDefaultValuesForModel extends Component $this->fields = CustomFieldset::find($this->fieldset_id)->fields; } - $this->add_default_values = ($this->model->defaultValues->count() > 0); + $this->add_default_values = (DefaultValuesForCustomFields::forPivot($this->model, Asset::class)->count() > 0); } public function updatedFieldsetId() diff --git a/app/Http/Livewire/Importer.php b/app/Http/Livewire/Importer.php index 7899182a60..8379b6985a 100644 --- a/app/Http/Livewire/Importer.php +++ b/app/Http/Livewire/Importer.php @@ -100,7 +100,7 @@ class Importer extends Component if ($type == "asset") { // add Custom Fields after a horizontal line $results['-'] = "———" . trans('admin/custom_fields/general.custom_fields') . "———’"; - foreach (CustomField::orderBy('name')->get() as $field) { + foreach (CustomField::where('type', \App\Models\Asset::class)->orderBy('name')->get() as $field) { // TODO - generalize? $results[$field->db_column_name()] = $field->name; } } diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php index a3b6aef90b..d11dd5f0c1 100644 --- a/app/Http/Middleware/TrustProxies.php +++ b/app/Http/Middleware/TrustProxies.php @@ -2,7 +2,7 @@ namespace App\Http\Middleware; -use Fideloper\Proxy\TrustProxies as Middleware; +use Illuminate\Http\Middleware\TrustProxies as Middleware; use Illuminate\Http\Request; class TrustProxies extends Middleware diff --git a/app/Http/Requests/CustomFieldRequest.php b/app/Http/Requests/CustomFieldRequest.php index 0c2ec0ae60..c8da020803 100644 --- a/app/Http/Requests/CustomFieldRequest.php +++ b/app/Http/Requests/CustomFieldRequest.php @@ -34,12 +34,14 @@ class CustomFieldRequest extends FormRequest case 'POST': { $rules['name'] = 'required|unique:custom_fields'; + $rules['tab'] = 'required'; break; } // Save all fields case 'PUT': $rules['name'] = 'required'; + $rules['tab'] = 'required'; break; // Save only what's passed diff --git a/app/Http/Transformers/CustomFieldsetsTransformer.php b/app/Http/Transformers/CustomFieldsetsTransformer.php index 61e42486ab..18f1f79632 100644 --- a/app/Http/Transformers/CustomFieldsetsTransformer.php +++ b/app/Http/Transformers/CustomFieldsetsTransformer.php @@ -3,6 +3,8 @@ namespace App\Http\Transformers; use App\Helpers\Helper; +use App\Models\Asset; +use App\Models\AssetModel; use App\Models\CustomFieldset; use Illuminate\Database\Eloquent\Collection; @@ -21,8 +23,13 @@ class CustomFieldsetsTransformer public function transformCustomFieldset(CustomFieldset $fieldset) { $fields = $fieldset->fields; - $models = $fieldset->models; + $models = []; $modelsArray = []; + if ($fieldset->type == Asset::class) { + \Log::debug("Item pivot id is: ".$fieldset->item_pivot_id); + $models = AssetModel::where('fieldset_id', $fieldset->id)->get(); + \Log::debug("And the models object count is: ".$models->count()); + } foreach ($models as $model) { $modelsArray[] = [ @@ -30,15 +37,21 @@ class CustomFieldsetsTransformer 'name' => e($model->name), ]; } + \Log::debug("Models array is: ".print_r($modelsArray,true)); $array = [ 'id' => (int) $fieldset->id, 'name' => e($fieldset->name), 'fields' => (new CustomFieldsTransformer)->transformCustomFields($fields, $fieldset->fields_count), - 'models' => (new DatatablesTransformer)->transformDatatables($modelsArray, $fieldset->models_count), + 'customizables' => (new DatatablesTransformer)->transformDatatables($fieldset->customizables(),count($fieldset->customizables())), 'created_at' => Helper::getFormattedDateObject($fieldset->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($fieldset->updated_at, 'datetime'), + 'type' => $fieldset->type, ]; + if ($fieldset->type == Asset::class) { + // TODO - removeme - legacy column just for Assets? + $array['models'] = (new DatatablesTransformer)->transformDatatables($modelsArray, count($modelsArray)); + } return $array; } diff --git a/app/Importer/AssetImporter.php b/app/Importer/AssetImporter.php index be19455618..b27e46e70d 100644 --- a/app/Importer/AssetImporter.php +++ b/app/Importer/AssetImporter.php @@ -23,19 +23,20 @@ class AssetImporter extends ItemImporter // ItemImporter handles the general fetching. parent::handle($row); + // FIXME : YUP!!!!! This shit needs to go (?) Yeah? if ($this->customFields) { foreach ($this->customFields as $customField) { - $customFieldValue = $this->array_smart_custom_field_fetch($row, $customField); + $customFieldValue = $this->array_smart_custom_field_fetch($row, $customField); // TODO/FIXME - this might require a new 'mode' on customFill()? if ($customFieldValue) { - if ($customField->field_encrypted == 1) { + if ($customField->field_encrypted == 1) { // FIXME - repeated code. $this->item['custom_fields'][$customField->db_column_name()] = \Crypt::encrypt($customFieldValue); $this->log('Custom Field '.$customField->name.': '.\Crypt::encrypt($customFieldValue)); } else { $this->item['custom_fields'][$customField->db_column_name()] = $customFieldValue; $this->log('Custom Field '.$customField->name.': '.$customFieldValue); } - } else { + } else { // FIXME - think this through? Do we want to blank this? Is that how other stuff works? // Clear out previous data. $this->item['custom_fields'][$customField->db_column_name()] = null; } diff --git a/app/Importer/Importer.php b/app/Importer/Importer.php index 4c60f0beed..956806f657 100644 --- a/app/Importer/Importer.php +++ b/app/Importer/Importer.php @@ -120,7 +120,7 @@ abstract class Importer * @author Daniel Meltzer * @since 5.0 */ - protected function populateCustomFields($headerRow) + protected function populateCustomFields($headerRow) // FIXME - what in the actual fuck is this. { // Stolen From https://adamwathan.me/2016/07/14/customizing-keys-when-mapping-collections/ // This 'inverts' the fields such that we have a collection of fields indexed by name. diff --git a/app/Models/Asset.php b/app/Models/Asset.php index 6792cf2859..0d8c0491fb 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -9,6 +9,8 @@ use App\Helpers\Helper; use App\Http\Traits\UniqueSerialTrait; use App\Http\Traits\UniqueUndeletedTrait; use App\Models\Traits\Acceptable; +use App\Models\Traits\Customizable; +use App\Models\Traits\HasCustomFields; use App\Models\Traits\Searchable; use App\Presenters\Presentable; use AssetPresenter; @@ -18,6 +20,7 @@ use DB; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Storage; use Watson\Validating\ValidatingTrait; @@ -38,8 +41,19 @@ class Asset extends Depreciable public const ASSET = 'asset'; public const USER = 'user'; - use Acceptable; + use Acceptable, HasCustomFields; + public function getFieldsetKey(): object|int|null { + return $this->model; + } + + public static function getFieldsetUsers(int $fieldset_id): array { + $models = []; + foreach(AssetModel::where("fieldset_id",$fieldset_id)->get() as $model) { + $models[route('models.show', $model->id)] = $model->name.(($model->model_number) ? ' ('.$model->model_number.')' : ''); + } + return $models; + } /** * Run after the checkout acceptance was declined by the user * @@ -187,40 +201,6 @@ class Asset extends Depreciable $this->attributes['expected_checkin'] = $value; } - /** - * This handles the custom field validation for assets - * - * @var array - */ - public function save(array $params = []) - { - if ($this->model_id != '') { - $model = AssetModel::find($this->model_id); - - if (($model) && ($model->fieldset)) { - - foreach ($model->fieldset->fields as $field){ - if($field->format == 'BOOLEAN'){ - $this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN); - } - } - - $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); - } - } - } - } - - - - return parent::save($params); - } - - public function getDisplayNameAttribute() { return $this->present()->name(); @@ -1515,7 +1495,7 @@ class Asset extends Depreciable * * In short, this set of statements tells the query builder to ONLY query against an * actual field that's being passed if it doesn't meet known relational fields. This - * allows us to query custom fields directly in the assetsv table + * allows us to query custom fields directly in the assets table * (regardless of their name) and *skip* any fields that we already know can only be * searched through relational searches that we do earlier in this method. * diff --git a/app/Models/AssetModel.php b/app/Models/AssetModel.php index aed1b925ef..cf965176c2 100755 --- a/app/Models/AssetModel.php +++ b/app/Models/AssetModel.php @@ -148,6 +148,7 @@ class AssetModel extends SnipeModel */ public function fieldset() { + // this is actually OK - we don't *need* to do this, but it's okay to make references from Model to fieldset return $this->belongsTo(\App\Models\CustomFieldset::class, 'fieldset_id'); } @@ -156,18 +157,6 @@ class AssetModel extends SnipeModel return $this->fieldset()->first()->fields(); } - /** - * Establishes the model -> custom field default values relationship - * - * @author hannah tinkler - * @since [v4.3] - * @return \Illuminate\Database\Eloquent\Relations\Relation - */ - public function defaultValues() - { - return $this->belongsToMany(\App\Models\CustomField::class, 'models_custom_fields')->withPivot('default_value'); - } - /** * Gets the full url for the image * diff --git a/app/Models/CustomField.php b/app/Models/CustomField.php index c98dbe637c..7ef3d2b1ac 100644 --- a/app/Models/CustomField.php +++ b/app/Models/CustomField.php @@ -16,7 +16,7 @@ class CustomField extends Model UniqueUndeletedTrait; /** - * Custom field predfined formats + * Custom field predefined formats * * @var array */ @@ -53,6 +53,7 @@ class CustomField extends Model 'field_encrypted' => 'nullable|boolean', 'auto_add_to_fieldsets' => 'boolean', 'show_in_listview' => 'boolean', + 'type' => 'required' ]; /** @@ -75,30 +76,19 @@ class CustomField extends Model ]; - /** - * This is confusing, since it's actually the custom fields table that - * we're usually modifying, but since we alter the assets table, we have to - * say that here, otherwise the new fields get added onto the custom fields - * table instead of the assets table. - * - * @author [Brady Wetherington] [] - * @since [v3.0] - */ - public static $table_name = 'assets'; - /** * Convert the custom field's name property to a db-safe string. * * We could probably have used str_slug() here but not sure what it would * do with previously existing values. - @snipe * - * @author [A. Gianotto] [] - * @since [v3.4] * @return string + * @since [v3.4] + * @author [A. Gianotto] [] */ public static function name_to_db_name($name) { - return '_snipeit_'.preg_replace('/[^a-zA-Z0-9]/', '_', strtolower($name)); + return '_snipeit_' . preg_replace('/[^a-zA-Z0-9]/', '_', strtolower($name)); } /** @@ -109,23 +99,22 @@ class CustomField extends Model * if they have changed, so we handle that here so that we don't have to remember * to do it in the controllers. * - * @author [A. Gianotto] [] - * @since [v3.4] * @return bool + * @since [v3.4] + * @author [A. Gianotto] [] */ public static function boot() { parent::boot(); self::created(function ($custom_field) { - // Column already exists on the assets table - nothing to do here. // This *shouldn't* happen in the wild. - if (Schema::hasColumn(self::$table_name, $custom_field->db_column)) { + if (Schema::hasColumn($custom_field->getTableName(), $custom_field->db_column)) { return false; } // Update the column name in the assets table - Schema::table(self::$table_name, function ($table) use ($custom_field) { + Schema::table($custom_field->getTableName(), function ($table) use ($custom_field) { $table->text($custom_field->convertUnicodeDbSlug())->nullable(); }); @@ -138,7 +127,7 @@ class CustomField extends Model // Column already exists on the assets table - nothing to do here. if ($custom_field->isDirty('name')) { - if (Schema::hasColumn(self::$table_name, $custom_field->convertUnicodeDbSlug())) { + if (Schema::hasColumn($custom_field->getTableName(), $custom_field->convertUnicodeDbSlug())) { return true; } @@ -148,7 +137,7 @@ class CustomField extends Model $platform->registerDoctrineTypeMapping('enum', 'string'); // Rename the field if the name has changed - Schema::table(self::$table_name, function ($table) use ($custom_field) { + Schema::table($custom_field->getTableName(), function ($table) use ($custom_field) { $table->renameColumn($custom_field->convertUnicodeDbSlug($custom_field->getOriginal('name')), $custom_field->convertUnicodeDbSlug()); }); @@ -164,12 +153,19 @@ class CustomField extends Model // Drop the assets column if we've deleted it from custom fields self::deleting(function ($custom_field) { - return Schema::table(self::$table_name, function ($table) use ($custom_field) { + return Schema::table($custom_field->getTableName(), function ($table) use ($custom_field) { $table->dropColumn($custom_field->db_column); }); }); } + public function getTableName() + { + $type = $this->type; + $instance = new $type(); + return $instance->getTable(); + } + /** * Establishes the customfield -> fieldset relationship * @@ -200,31 +196,23 @@ class CustomField extends Model } /** - * Establishes the customfield -> default values relationship - * - * @author Hannah Tinkler - * @since [v3.0] - * @return \Illuminate\Database\Eloquent\Relations\Relation - */ - public function defaultValues() - { - return $this->belongsToMany(\App\Models\AssetModel::class, 'models_custom_fields')->withPivot('default_value'); - } - - /** - * Returns the default value for a given model using the defaultValues + * Returns the default value for a given 'item' using the defaultValues * relationship * * @param int $modelId * @return string */ - public function defaultValue($modelId) + public function defaultValue($pivot_id) { - return $this->defaultValues->filter(function ($item) use ($modelId) { - return $item->pivot->asset_model_id == $modelId; - })->map(function ($item) { - return $item->pivot->default_value; - })->first(); + /* + below, you might think you need to add: + + where('type', $this->type), + + but the type can be inferred from by the custom_field itself (which also has a type) + can't use forPivot() here because we don't have an object yet. (TODO?) + */ + DefaultValuesForCustomFields::where('item_pivot_id', $pivot_id)->where('custom_field_id',$this->id)->first()?->default_value; //TODO - php8-only operator! } /** diff --git a/app/Models/CustomFieldset.php b/app/Models/CustomFieldset.php index a2698d818c..9a291d2288 100644 --- a/app/Models/CustomFieldset.php +++ b/app/Models/CustomFieldset.php @@ -20,6 +20,7 @@ class CustomFieldset extends Model */ public $rules = [ 'name' => 'required|unique:custom_fieldsets', + '' ]; /** @@ -48,11 +49,13 @@ class CustomFieldset extends Model * * @author [Brady Wetherington] [] * @since [v3.0] - * @return \Illuminate\Database\Eloquent\Relations\Relation + * @return \Illuminate\Database\Eloquent\Collection */ - public function models() + public function customizables() // TODO - I don't like this name, but I can't think of anything better { - return $this->hasMany(\App\Models\AssetModel::class, 'fieldset_id'); + $customizable_class_name = $this->type; //TODO - copypasta from Customizable trait? + \Log::debug("Customizable Class name is: ".$customizable_class_name); + return $customizable_class_name::getFieldsetUsers($this->id); } /** @@ -77,6 +80,7 @@ class CustomFieldset extends Model */ public function validation_rules() { + \Log::debug("CALLING validation_rules FOR customfiledsets!"); $rules = []; foreach ($this->fields as $field) { $rule = []; @@ -90,7 +94,12 @@ class CustomFieldset extends Model $rule[] = 'unique_undeleted'; } - array_push($rule, $field->attributes['format']); + \Log::debug("Field Format for".$field->name." is: ".$field->format); + if($field->format == 'DATE') { //we do a weird mutator thing, it's confusing - but, yes, it's all-caps + $rule[] = 'date_format:Y-m-d'; + } else { + array_push($rule, $field->attributes['format']); + } $rules[$field->db_column_name()] = $rule; } diff --git a/app/Models/DefaultValuesForCustomFields.php b/app/Models/DefaultValuesForCustomFields.php new file mode 100644 index 0000000000..884ff9b3b2 --- /dev/null +++ b/app/Models/DefaultValuesForCustomFields.php @@ -0,0 +1,34 @@ + 'required' + ]; + + public $timestamps = false; + + public function field() { + return $this->belongsTo('custom_fields'); + } + + // There is, effectively, another 'relation' here, but it's weirdly polymorphic + // and impossible to represent in Laravel. + // we have a 'type', and we have an 'item_pivot_id' - + // For example, in Assets the 'type' would be App\Models\Asset, and the 'item_pivot_id' would be a model_id + // I can't come up with any way to represent this in Laravel/Eloquent + + // TODO: might be getting overly-fancy here; maybe just want to do an ID? Instead of an Eloquent Model? + public function scopeForPivot(Builder $query, Model $item, string $class) { + return $query->where('item_pivot_id', $item->id)->where('type', $class); + } +} diff --git a/app/Models/Traits/HasCustomFields.php b/app/Models/Traits/HasCustomFields.php new file mode 100644 index 0000000000..b3f8b52977 --- /dev/null +++ b/app/Models/Traits/HasCustomFields.php @@ -0,0 +1,138 @@ +fieldset` or `->id`. + */ + public function getFieldset(): ?CustomFieldset { + $pivot = $this->getFieldsetKey(); + if(is_int($pivot)) { //why does this look just like the other thing? (below, look for is_int() + return Fieldset::find($pivot); + } + return $pivot->fieldset; + } + + /********************** + * @return Object|int|null + * (if this is in PHP 8.0, can we just put that as the signature?) + * + * This is the main method you have to override. It should either return an + * Object who you can call `->fieldset` on and get a fieldset object, and also + * be able to call `->id` on to get a unique key to be able to show custom fields. + * For example, for Assets, the element that is returned is the 'model' for the Asset. + * For something like Users, which will probably have only one universal set of custom fields, + * it should just return the Fieldset ID for it. Or, if there are no custom fields, it should + * return null + */ + abstract public function getFieldsetKey(): Object|int|null; // php v8 minimum, GOOD. TODO + + /*********************** + * @param int $fieldset_id + * @return Collection + * + * This is the main method you need to override to return a list of things that are *using* this fieldset + * The format is an array with keys: a URL, and values. So, for assets, it might return + * { + * "models/14" => "MacBook Pro 13 (model no: 12345)" + * } + */ + abstract public static function getFieldsetUsers(int $fieldset_id): array; + + public static function augmentValidationRulesForCustomFields($model) { + \Log::debug("Augmenting validation rules for custom fields!!!!!!"); + $fieldset = $model->getFieldset(); + if ($fieldset) { + foreach ($fieldset->fields as $field){ + if($field->format == 'BOOLEAN'){ // TODO - this 'feels' like entanglement of concerns? + $model->{$field->db_column} = filter_var($model->{$model->db_column}, FILTER_VALIDATE_BOOLEAN); + } + } + + if(!$model->rules) { + $model->rules = []; + } + $model->rules += $model->getFieldset()->validation_rules(); + \Log::debug("FINAL RULES ARE: ".print_r($model->rules,true)); + } + + } + + public function getDefaultValue(CustomField $field) + { + $pivot = $this->getFieldsetKey(); // TODO - feels copypasta-ish? + $key_id = null; + + if( is_int($pivot) ) { // TODO: *WHY* does this code repeat?! + $key_id = $pivot; // now we're done + } elseif( is_object($pivot) ) { + $key_id = $pivot?->id; + } + if(is_null($key_id)) { + return; + } + + // TODO - begninng to think my custom scope really should be just an integer :/ + return DefaultValuesForCustomFields::where('type',self::class) + ->where('custom_field_id',$field->id) + ->where('item_pivot_id',$key_id)->first()?->default_value; + } + + public function customFill(Request $request, User $user, bool $shouldSetDefaults = false) { + $this->_filled = true; + if ($this->getFieldset()) { + foreach ($this->getFieldset()->fields as $field) { + if (is_array($request->input($field->db_column))) { + $field_value = implode(', ', $request->input($field->db_column)); + } else { + $field_value = $request->input($field->db_column); + } + + if ($shouldSetDefaults && (is_null($field_value) || $field_value === '')) { + $field_value = $this->getDefaultValue($field); + } + if ($field->field_encrypted == '1') { + if ($user->can('admin')) { + $this->{$field->db_column} = \Crypt::encrypt($field_value); + } + } else { + $this->{$field->db_column} = $request->input($field->db_column); + } + } + } + } +} \ No newline at end of file diff --git a/app/Presenters/AssetPresenter.php b/app/Presenters/AssetPresenter.php index ec50933583..5302d9f1a0 100644 --- a/app/Presenters/AssetPresenter.php +++ b/app/Presenters/AssetPresenter.php @@ -2,7 +2,9 @@ namespace App\Presenters; +use App\Models\Asset; use App\Models\CustomField; +use App\Models\CustomFieldset; use DateTime; /** @@ -282,10 +284,16 @@ class AssetPresenter extends Presenter // models. We only pass the fieldsets that pertain to each asset (via their model) so that we // don't junk up the REST API with tons of custom fields that don't apply - $fields = CustomField::whereHas('fieldset', function ($query) { - $query->whereHas('models'); - })->get(); + //only get fieldsets that have fields + $fieldsets = CustomFieldset::where("type", Asset::class)->whereHas('fields')->get(); + $ids = []; + foreach($fieldsets as $fieldset) { + if (count($fieldset->customizables()) > 0) { //only get fieldsets that are 'in use' + $ids[] = $fieldset->id; + } + } + $fields = CustomField::whereIn('id',$ids)->get(); // Note: We do not need to e() escape the field names here, as they are already escaped when // they are presented in the blade view. If we escape them here, custom fields with quotes in their // name can break the listings page. - snipe diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 9d493e85bb..1474f87421 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -87,7 +87,7 @@ class AuthServiceProvider extends ServiceProvider ]); $this->registerPolicies(); - Passport::routes(); +// Passport::routes(); Passport::tokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years'))); Passport::refreshTokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years'))); Passport::personalAccessTokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years'))); diff --git a/config/app.php b/config/app.php index 2559b8012c..3c46f309a1 100755 --- a/config/app.php +++ b/config/app.php @@ -289,7 +289,7 @@ return [ Intervention\Image\ImageServiceProvider::class, Collective\Html\HtmlServiceProvider::class, Spatie\Backup\BackupServiceProvider::class, - Fideloper\Proxy\TrustedProxyServiceProvider::class, +// Fideloper\Proxy\TrustedProxyServiceProvider::class, PragmaRX\Google2FALaravel\ServiceProvider::class, Laravel\Passport\PassportServiceProvider::class, Laravel\Tinker\TinkerServiceProvider::class, diff --git a/config/trustedproxy.php b/config/trustedproxy.php index a3d80418ba..b46cb2585e 100644 --- a/config/trustedproxy.php +++ b/config/trustedproxy.php @@ -1,5 +1,7 @@ Illuminate\Http\Request::HEADER_X_FORWARDED_ALL, + 'headers' => Request::HEADER_X_FORWARDED_FOR | + Request::HEADER_X_FORWARDED_HOST | + Request::HEADER_X_FORWARDED_PORT | + Request::HEADER_X_FORWARDED_PROTO | + Request::HEADER_X_FORWARDED_AWS_ELB, + + ]; diff --git a/database/factories/AssetFactory.php b/database/factories/AssetFactory.php index 0e0c3931d8..2ecb9f7c44 100644 --- a/database/factories/AssetFactory.php +++ b/database/factories/AssetFactory.php @@ -338,4 +338,15 @@ class AssetFactory extends Factory { return $this->state(['requestable' => false]); } + + public function withComplicatedCustomFields() + { + return $this->state(function () { + return [ + 'model_id' => function () { + return AssetModel::where('name','complicated')->first() ?? AssetModel::factory()->complicated(); + } + ]; + }); + } } diff --git a/database/factories/AssetModelFactory.php b/database/factories/AssetModelFactory.php index 4881d6560b..d149afb9ae 100644 --- a/database/factories/AssetModelFactory.php +++ b/database/factories/AssetModelFactory.php @@ -3,6 +3,7 @@ namespace Database\Factories; use App\Models\AssetModel; +use App\Models\CustomField; use App\Models\CustomFieldset; use App\Models\Depreciation; use App\Models\Manufacturer; @@ -429,4 +430,13 @@ class AssetModelFactory extends Factory ]; }); } + + public function complicated() + { + return $this->state(function () { + return [ + 'name' => 'Complicated fieldset' + ]; + })->for(CustomFieldSet::factory()->complicated(),'fieldset'); + } } diff --git a/database/factories/CustomFieldFactory.php b/database/factories/CustomFieldFactory.php index adcca9cae1..a9d9003765 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', + 'type' => 'App\\Models\\Asset' ]; } @@ -74,9 +75,28 @@ class CustomFieldFactory extends Factory { return $this->state(function () { return [ - 'name' => 'MAC Address', + 'name' => 'MAC Address EXPLICIT', 'format' => 'regex:/^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$/', ]; }); } + + public function plainText() + { + return $this->state(function () { + return [ + 'name' => 'plain_text', + ]; + }); + } + + public function date() + { + return $this->state(function () { + return [ + 'name' => 'date', + 'format' => 'date' + ]; + }); + } } diff --git a/database/factories/CustomFieldsetFactory.php b/database/factories/CustomFieldsetFactory.php index f369121c8f..80c435dd6c 100644 --- a/database/factories/CustomFieldsetFactory.php +++ b/database/factories/CustomFieldsetFactory.php @@ -2,6 +2,7 @@ namespace Database\Factories; +use App\Models\CustomField; use App\Models\CustomFieldset; use Illuminate\Database\Eloquent\Factories\Factory; @@ -43,4 +44,16 @@ class CustomFieldsetFactory extends Factory ]; }); } + + public function complicated() + { + //$mac = CustomField::factory()->macAddress()->create(); + return $this->state(function () { + return [ + 'name' => 'complicated' + ]; + }) ->hasAttached(CustomField::factory()->macAddress(),['required' => false, 'order' => 0],'fields') + ->hasAttached(CustomField::factory()->plainText(),['required' => true,'order' => 1],'fields') + ->hasAttached(CustomField::factory()->date(),['required' => false, 'order' => 2],'fields'); + } } 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 4725cccfe1..2aa036bc09 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 @@ -23,8 +23,8 @@ function updateLegacyColumnName($customfield) $name_to_db_name = CustomField::name_to_db_name($customfield->name); //\Log::debug('Trying to rename '.$name_to_db_name." to ".$customfield->convertUnicodeDbSlug()."...\n"); - if (Schema::hasColumn(CustomField::$table_name, $name_to_db_name)) { - return Schema::table(CustomField::$table_name, + if (Schema::hasColumn('assets', $name_to_db_name)) { + return Schema::table('assets', function ($table) use ($name_to_db_name, $customfield) { $table->renameColumn($name_to_db_name, $customfield->convertUnicodeDbSlug()); } @@ -81,8 +81,8 @@ class FixUtf8CustomFieldColumnNames extends Migration // "_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) { + if (Schema::hasColumn('assets', $currentColumnName)) { + Schema::table('assets', function (Blueprint $table) use ($currentColumnName, $legacyColumnName) { $table->renameColumn( $currentColumnName, $legacyColumnName diff --git a/database/migrations/2021_06_29_212602_add_type_to_custom_fields.php b/database/migrations/2021_06_29_212602_add_type_to_custom_fields.php new file mode 100644 index 0000000000..e583d6d413 --- /dev/null +++ b/database/migrations/2021_06_29_212602_add_type_to_custom_fields.php @@ -0,0 +1,38 @@ +text('type')->default('App\\Models\\Asset'); // TODO this default is needed for a not-nullable column, I guess? I don't like this because it will silently 'fix' errors we should properly 'fix' + }); + CustomField::query()->update(['type' => Asset::class]); // TODO - is this still necessary with that 'default'? + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('custom_fields', function (Blueprint $table) { + // + $table->dropColumn('type'); + }); + } +} diff --git a/database/migrations/2021_06_29_212612_add_type_to_custom_fieldsets.php b/database/migrations/2021_06_29_212612_add_type_to_custom_fieldsets.php new file mode 100644 index 0000000000..e9302abb43 --- /dev/null +++ b/database/migrations/2021_06_29_212612_add_type_to_custom_fieldsets.php @@ -0,0 +1,38 @@ +text('type')->default('App\\Models\\Asset'); + }); + CustomFieldset::query()->update(['type' => Asset::class]); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('custom_fieldsets', function (Blueprint $table) { + // + $table->dropColumn('type'); + }); + } +} diff --git a/database/migrations/2023_08_10_121812_generalize_default_values_for_custom_fields.php b/database/migrations/2023_08_10_121812_generalize_default_values_for_custom_fields.php new file mode 100644 index 0000000000..46d1ad2a2a --- /dev/null +++ b/database/migrations/2023_08_10_121812_generalize_default_values_for_custom_fields.php @@ -0,0 +1,28 @@ +text('type')->default('App\\Models\\Asset'); + $table->renameColumn('asset_model_id','item_pivot_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('default_values_for_custom_fields', function (Blueprint $table) { + $table->dropColumn('type'); + $table->renameColumn('item_pivot_id','asset_model_id'); + }); + } +} diff --git a/resources/views/custom_fields/fields/edit.blade.php b/resources/views/custom_fields/fields/edit.blade.php index ac945d8597..ad833223cb 100644 --- a/resources/views/custom_fields/fields/edit.blade.php +++ b/resources/views/custom_fields/fields/edit.blade.php @@ -30,6 +30,8 @@ @endif @csrf + +
diff --git a/resources/views/custom_fields/fieldsets/edit.blade.php b/resources/views/custom_fields/fieldsets/edit.blade.php index 7a35ca146f..4520b27776 100644 --- a/resources/views/custom_fields/fieldsets/edit.blade.php +++ b/resources/views/custom_fields/fieldsets/edit.blade.php @@ -12,6 +12,7 @@ @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 141d715344..bd22b621b4 100644 --- a/resources/views/custom_fields/index.blade.php +++ b/resources/views/custom_fields/index.blade.php @@ -13,6 +13,23 @@ @section('content') @can('view', \App\Models\CustomFieldset::class) +
+
+
+
+ + +
+
+
+
@@ -21,7 +38,7 @@

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

@@ -47,7 +64,7 @@ {{ trans('general.name') }} {{ trans('admin/custom_fields/general.qty_fields') }} - {{ trans('admin/custom_fields/general.used_by_models') }} + {{ trans('admin/custom_fields/general.used_by_models') }}{{-- FIXME --}} {{ trans('table.actions') }} @@ -63,9 +80,9 @@ {{ $fieldset->fields->count() }} - @foreach($fieldset->models as $model) - {{ $model->name }}{{ ($model->model_number) ? ' ('.$model->model_number.')' : '' }} - + @foreach($fieldset->customizables() as $url => $name) + {{ $name }} + {{-- get_class($customizable) }}: {{ $customizable->name
--}} @endforeach @@ -81,7 +98,7 @@ @can('delete', $fieldset) {{ Form::open(['route' => array('fieldsets.destroy', $fieldset->id), 'method' => 'delete','style' => 'display:inline-block']) }} - @if($fieldset->models->count() > 0) + @if(count($fieldset->customizables()) > 0 /* TODO - hate 'customizables' */) @else @@ -111,7 +128,7 @@

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

diff --git a/tests/Unit/HasCustomFieldsTraitTest.php b/tests/Unit/HasCustomFieldsTraitTest.php new file mode 100644 index 0000000000..35a8ad0708 --- /dev/null +++ b/tests/Unit/HasCustomFieldsTraitTest.php @@ -0,0 +1,79 @@ +withComplicatedCustomFields()->create(); + + $this->assertEquals($asset->model->fieldset->fields->count(), 3,'Custom Fieldset should have exactly 3 custom fields'); + $this->assertTrue(Schema::hasColumn('assets','_snipeit_mac_address_explicit_2'),'Assets table should have MAC address column'); + $this->assertTrue(Schema::hasColumn('assets','_snipeit_plain_text_3'),'Assets table should have MAC address column'); + $this->assertTrue(Schema::hasColumn('assets','_snipeit_date_4'),'Assets table should have MAC address column'); + } + public function testRequired() + { + $asset = Asset::factory()->withComplicatedCustomFields()->create(); + $this->assertFalse($asset->save(),'save() should fail due to required text field'); + } + + public function testFormat() + { + $asset = Asset::factory()->withComplicatedCustomFields()->make(); + $asset->_snipeit_plain_text_3 = 'something'; + $asset->_snipeit_mac_address_explicit_2 = 'fartsssssss'; + $this->assertFalse($asset->save(), 'should fail due to bad MAC address'); + } + + public function testDate() + { +// \Log::error("uh, what the heck is going on here?!"); + $asset = Asset::factory()->withComplicatedCustomFields()->make(); + $asset->_snipeit_plain_text_3 = 'some text'; + $asset->_snipeit_date_4 = '1/2/2023'; +// $asset->save(); +// dd($asset); + $this->assertFalse($asset->save(),'Should fail due to incorrectly formatted date.'); + } + + public function testSaveMinimal() + { + $asset = Asset::factory()->withComplicatedCustomFields()->make(); + $asset->_snipeit_plain_text_3 = "some text"; + $this->assertTrue($asset->save(),"Asset should've saved okay, the one required field was filled out"); + } + + public function testSaveMaximal() + { + $asset = Asset::factory()->withComplicatedCustomFields()->make(); + $asset->_snipeit_plain_text_3 = "some text"; + $asset->_snipeit_date_4 = "2023-01-02"; + $asset->_snipeit_mac_address_explicit_2 = "ff:ff:ff:ff:ff:ff"; + $this->assertTrue($asset->save(),"Asset should've saved okay, the one required field was filled out, and so were the others"); + } + + public function testJsonPost() + { + $asset = Asset::factory()->withComplicatedCustomFields()->make(); + $response = $this->postJson('/api/v1/hardware', [ + + ]); + $response->assertStatus(200); + } + +}