From d2b7828569ca92a54c2f81cb06d154d1d27ef6f6 Mon Sep 17 00:00:00 2001 From: Brady Wetherington Date: Thu, 6 Jun 2024 13:35:38 +0100 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 | 70 +-------- .../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/Requests/CustomFieldRequest.php | 2 + .../CustomFieldsetsTransformer.php | 17 ++- app/Importer/AssetImporter.php | 5 +- app/Importer/Importer.php | 2 +- app/Models/Asset.php | 54 +++---- app/Models/AssetModel.php | 13 +- app/Models/CustomField.php | 71 ++++----- 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 | 1 - config/trustedproxy.php | 9 +- database/factories/AssetFactory.php | 11 ++ database/factories/AssetModelFactory.php | 9 ++ database/factories/CustomFieldFactory.php | 21 ++- database/factories/CustomFieldsetFactory.php | 26 +++- ...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 | 45 ++++-- tests/Unit/HasCustomFieldsTraitTest.php | 79 ++++++++++ 36 files changed, 634 insertions(+), 244 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 1ac2a90a34..b222bdf5a3 100644 --- a/app/Helpers/Helper.php +++ b/app/Helpers/Helper.php @@ -651,6 +651,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 f1fa5369d1..324677223a 100644 --- a/app/Http/Controllers/Api/AssetModelsController.php +++ b/app/Http/Controllers/Api/AssetModelsController.php @@ -67,7 +67,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 dc20588b75..ee74169f16 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -120,7 +120,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(); } @@ -586,48 +586,8 @@ class AssetsController extends Controller $asset = $request->handleImages($asset); - // Update custom fields in the database. - $model = AssetModel::find($request->input('model_id')); + $asset->customFill($request, Auth::user(), true); - // Check that it's an object and not a collection - // (Sometimes people send arrays here and they shouldn't - if (($model) && ($model instanceof AssetModel) && ($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)); - } - } - } - if ($field->element == 'checkbox') { - if(is_array($field_val)) { - $field_val = implode(',', $field_val); - } - } - - - $asset->{$field->db_column} = $field_val; - } - } if ($asset->save()) { if ($request->get('assigned_user')) { @@ -689,32 +649,8 @@ class AssetsController extends Controller $asset = $request->handleImages($asset); $model = AssetModel::find($asset->model_id); - - // Update custom fields - $problems_updating_encrypted_custom_fields = false; - if (($model) && (isset($model->fieldset))) { - foreach ($model->fieldset->fields as $field) { - $field_val = $request->input($field->db_column, null); - - if ($request->has($field->db_column)) { - if ($field->element == 'checkbox') { - if(is_array($field_val)) { - $field_val = implode(',', $field_val); - } - } - if ($field->field_encrypted == '1') { - if (Gate::allows('admin')) { - $field_val = Crypt::encrypt($field_val); - } else { - $problems_updating_encrypted_custom_fields = true; - continue; - } - } - $asset->{$field->db_column} = $field_val; - } - } - } + $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 2fa22f5491..17026ccb2d 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 3dfb28feb3..6b86a26c78 100755 --- a/app/Http/Controllers/AssetModelsController.php +++ b/app/Http/Controllers/AssetModelsController.php @@ -9,6 +9,7 @@ use App\Models\Asset; use App\Models\AssetModel; use App\Models\CustomField; use App\Models\User; +use App\Models\DefaultValuesForCustomFields; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\View; @@ -163,7 +164,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(); $model->fieldset_id = $request->input('fieldset_id'); @@ -480,7 +481,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 @@ -515,22 +516,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 bf3a3ec3a8..0a069e7ba6 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -160,29 +160,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 94b45552fd..1c9513d65d 100644 --- a/app/Http/Controllers/CustomFieldsController.php +++ b/app/Http/Controllers/CustomFieldsController.php @@ -9,6 +9,7 @@ use App\Models\CustomFieldset; use Illuminate\Support\Facades\Auth; use Illuminate\Http\Request; + /** * This controller handles all actions related to Custom Asset Fields for * the Snipe-IT Asset Management application. @@ -20,6 +21,7 @@ use Illuminate\Http\Request; */ class CustomFieldsController extends Controller { + /** * Returns a view with a listing of custom fields. * @@ -28,12 +30,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 "auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0), "show_in_listview" => $request->get("show_in_listview", 0), "show_in_requestable_list" => $request->get("show_in_requestable_list", 0), - "user_id" => Auth::id() + "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']); @@ -290,7 +294,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 e4382a31ea..cab5e86f36 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 318514af73..81ad14ecd7 100644 --- a/app/Http/Livewire/Importer.php +++ b/app/Http/Livewire/Importer.php @@ -109,7 +109,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/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 594ca58f0d..c4a0734ca6 100644 --- a/app/Importer/AssetImporter.php +++ b/app/Importer/AssetImporter.php @@ -28,9 +28,10 @@ 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) { @@ -40,7 +41,7 @@ class AssetImporter extends ItemImporter $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 c7b0e96820..2ea52da927 100644 --- a/app/Importer/Importer.php +++ b/app/Importer/Importer.php @@ -175,7 +175,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 09dae1a1c3..6ed8568973 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -8,6 +8,8 @@ use App\Exceptions\CheckoutNotAllowed; use App\Helpers\Helper; 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; @@ -17,6 +19,7 @@ use Illuminate\Support\Facades\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; @@ -37,8 +40,21 @@ 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 * @@ -200,41 +216,7 @@ 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(); - - if ($this->model->fieldset){ - foreach ($this->model->fieldset->fields as $field){ - if($field->format == 'BOOLEAN'){ - $this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN); - } - } - } - } - } - - return parent::save($params); - } - - + public function getDisplayNameAttribute() { return $this->present()->name(); diff --git a/app/Models/AssetModel.php b/app/Models/AssetModel.php index c5fb9284aa..f7e08d6f25 100755 --- a/app/Models/AssetModel.php +++ b/app/Models/AssetModel.php @@ -142,6 +142,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'); } @@ -150,18 +151,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 c1826a94d8..ec6f486a7d 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 */ @@ -82,30 +82,19 @@ class CustomField extends Model 'show_in_requestable_list', ]; - /** - * 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)); } /** @@ -116,23 +105,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(); }); @@ -145,7 +133,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; } @@ -155,7 +143,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()); }); @@ -171,12 +159,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 * @@ -207,31 +202,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 71be28e8a3..3a36a417c2 100644 --- a/app/Models/CustomFieldset.php +++ b/app/Models/CustomFieldset.php @@ -22,6 +22,7 @@ class CustomFieldset extends Model */ public $rules = [ 'name' => 'required|unique:custom_fieldsets', + '' ]; /** @@ -50,11 +51,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); } /** @@ -79,6 +82,7 @@ class CustomFieldset extends Model */ public function validation_rules() { + \Log::debug("CALLING validation_rules FOR customfiledsets!"); $rules = []; foreach ($this->fields as $field) { $rule = []; @@ -92,7 +96,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; // add not_array to rules for all fields but checkboxes 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 163ee1b606..2b40395405 100644 --- a/app/Presenters/AssetPresenter.php +++ b/app/Presenters/AssetPresenter.php @@ -2,8 +2,10 @@ namespace App\Presenters; +use App\Models\Asset; use App\Models\CustomField; use Carbon\CarbonImmutable; +use App\Models\CustomFieldset; use DateTime; /** @@ -299,10 +301,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 51e6858c9c..19da367a08 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -87,7 +87,6 @@ class AuthServiceProvider extends ServiceProvider ]); $this->registerPolicies(); - //Passport::routes(); //this is no longer required in newer passport versions 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/trustedproxy.php b/config/trustedproxy.php index 106a13d4f5..cf011faa93 100644 --- a/config/trustedproxy.php +++ b/config/trustedproxy.php @@ -1,5 +1,7 @@ Illuminate\Http\Request::HEADER_X_FORWARDED_ALL, //this is mostly handled already - + '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 43845c3075..67470b2be5 100644 --- a/database/factories/AssetFactory.php +++ b/database/factories/AssetFactory.php @@ -387,4 +387,15 @@ class AssetFactory extends Factory $asset->setValidating(true); }); } + + 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 6790897567..6a50e5d933 100644 --- a/database/factories/AssetModelFactory.php +++ b/database/factories/AssetModelFactory.php @@ -448,4 +448,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 44ab0707e0..05247c013b 100644 --- a/database/factories/CustomFieldFactory.php +++ b/database/factories/CustomFieldFactory.php @@ -27,6 +27,7 @@ class CustomFieldFactory extends Factory 'element' => 'text', 'auto_add_to_fieldsets' => '0', 'show_in_requestable_list' => '0', + 'type' => 'App\\Models\\Asset' ]; } @@ -76,7 +77,7 @@ 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}$/', ]; }); @@ -93,6 +94,15 @@ class CustomFieldFactory extends Factory }); } + public function plainText() + { + return $this->state(function () { + return [ + 'name' => 'plain_text', + ]; + }); + } + public function testCheckbox() { return $this->state(function () { @@ -117,4 +127,13 @@ class CustomFieldFactory extends Factory }); } + 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 a9e8b9ae12..7ac31ce38e 100644 --- a/database/factories/CustomFieldsetFactory.php +++ b/database/factories/CustomFieldsetFactory.php @@ -2,8 +2,8 @@ namespace Database\Factories; -use App\Models\CustomFieldset; use App\Models\CustomField; +use App\Models\CustomFieldset; use Illuminate\Database\Eloquent\Factories\Factory; class CustomFieldsetFactory extends Factory @@ -58,13 +58,13 @@ class CustomFieldsetFactory extends Factory { return $this->afterCreating(function (CustomFieldset $fieldset) use ($fields) { if (empty($fields)) { - $mac_address = CustomField::factory()->macAddress()->create(); - $ram = CustomField::factory()->ram()->create(); - $cpu = CustomField::factory()->cpu()->create(); + $mac_address = CustomField::factory()->macAddress()->create(); + $ram = CustomField::factory()->ram()->create(); + $cpu = CustomField::factory()->cpu()->create(); - $fieldset->fields()->attach($mac_address, ['order' => '1', 'required' => false]); - $fieldset->fields()->attach($ram, ['order' => '2', 'required' => false]); - $fieldset->fields()->attach($cpu, ['order' => '3', 'required' => false]); + $fieldset->fields()->attach($mac_address, ['order' => '1', 'required' => false]); + $fieldset->fields()->attach($ram, ['order' => '2', 'required' => false]); + $fieldset->fields()->attach($cpu, ['order' => '3', 'required' => false]); } else { foreach ($fields as $field) { $fieldset->fields()->attach($field, ['order' => '1', 'required' => false]); @@ -72,4 +72,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 e21e9fba48..b47a73f3fb 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 d8eeae1d56..9119031d00 100644 --- a/resources/views/custom_fields/index.blade.php +++ b/resources/views/custom_fields/index.blade.php @@ -13,6 +13,26 @@ @section('content') @can('view', \App\Models\CustomFieldset::class) +
+
+
+
+ + +
+
+
+
@@ -21,7 +41,9 @@

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

@@ -47,7 +69,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 +85,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 @@ -88,10 +110,13 @@ @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 - + @endif {{ Form::close() }} @endcan @@ -118,7 +143,9 @@

{{ 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); + } + +}