Compare commits

...

8 Commits

Author SHA1 Message Date
Brady Wetherington
251080d5b8 Make tests pass, at least to start with. Still needs cleanup 2024-06-06 17:12:26 +01:00
Brady Wetherington
3a3f26ba8b Clean up the fieldset experience for custom fields for users 2024-06-06 13:56:04 +01:00
Brady Wetherington
58656fbaec Clean up this migration so it runs forwards and backwards OK 2024-06-06 13:49:14 +01:00
Brady Wetherington
873d260f55 Refactor out common code for 'custom fields view' partial 2024-06-06 13:49:12 +01:00
Brady Wetherington
48fd6f3f26 Remove some code duplication 2024-06-06 13:47:34 +01:00
Brady Wetherington
05a68afab9 Wiring up custom fields for users - still some big UI challenges tho 2024-06-06 13:47:32 +01:00
Brady Wetherington
21ff0e748c Got a chunk of Custom Fields for Users worked out, still needs cleanup 2024-06-06 13:45:35 +01:00
Brady Wetherington
d2b7828569 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
2024-06-06 13:35:38 +01:00
50 changed files with 1008 additions and 382 deletions

View File

@@ -0,0 +1,77 @@
<?php
namespace App\Helpers;
use Illuminate\Support\Facades\Gate;
/*********************
* These two helper methods are more designed for being re-used with the new HasCustomFields Trait
*
* The 'transform' method is designed for BlahTransformer things that need to return custom field values.
*
* The 'present' method is designed for when you're trying to generate fieldlists for use in Bootstrap tables
* - typically the 'dataTableLayout' method
*
*********************/
class CustomFieldHelper {
static function transform($fieldset, $item) {
if ($fieldset && ($fieldset->fields->count() > 0)) {
$fields_array = [];
foreach ($fieldset->fields as $field) {
if ($field->isFieldDecryptable($item->{$field->db_column})) {
$decrypted = Helper::gracefulDecrypt($field, $item->{$field->db_column});
$value = (Gate::allows('assets.view.encrypted_custom_fields')) ? $decrypted : strtoupper(trans('admin/custom_fields/general.encrypted'));
if ($field->format == 'DATE'){
if (Gate::allows('assets.view.encrypted_custom_fields')){
$value = Helper::getFormattedDateObject($value, 'date', false);
} else {
$value = strtoupper(trans('admin/custom_fields/general.encrypted'));
}
}
$fields_array[$field->name] = [
'field' => e($field->db_column),
'value' => e($value),
'field_format' => $field->format,
'element' => $field->element,
];
} else {
$value = $item->{$field->db_column};
if (($field->format == 'DATE') && (!is_null($value)) && ($value!='')){
$value = Helper::getFormattedDateObject($value, 'date', false);
}
$fields_array[$field->name] = [
'field' => e($field->db_column),
'value' => e($value),
'field_format' => $field->format,
'element' => $field->element,
];
}
return $fields_array;
}
} else {
return new \stdClass; // HACK to force generation of empty object instead of empty list
}
}
static function present($field) {
return [
'field' => 'custom_fields.'.$field->db_column,
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => $field->name,
'formatter'=> 'customFieldsFormatter',
'escape' => true,
'class' => ($field->field_encrypted == '1') ? 'css-padlock' : '',
'visible' => ($field->show_in_listview == '1') ? true : false,
];
}
}

View File

@@ -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
*

View File

@@ -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') {

View File

@@ -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')) {
@@ -688,33 +648,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;
}
}
}
$problems_updating_encrypted_custom_fields = !$asset->customFill($request, Auth::user());
if ($asset->save()) {
if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {

View File

@@ -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)) {

View File

@@ -14,6 +14,7 @@ use App\Http\Transformers\UsersTransformer;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Company;
use App\Models\CustomField;
use App\Models\License;
use App\Models\User;
use App\Notifications\CurrentInventory;
@@ -37,7 +38,7 @@ class UsersController extends Controller
{
$this->authorize('view', User::class);
$users = User::select([
$allowed_columns = [
'users.activated',
'users.created_by',
'users.address',
@@ -75,7 +76,12 @@ class UsersController extends Controller
'users.autoassign_licenses',
'users.website',
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy')
];
foreach (CustomField::where('type', User::class)->get() as $field) {
$allowed_columns[] = $field->db_column_name();
}
$users = User::select($allowed_columns)->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy',)
->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'managesUsers as manages_users_count', 'managedLocations as manages_locations_count');
@@ -396,7 +402,9 @@ class UsersController extends Controller
}
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
$user->customFill($request,Auth::user());
if ($user->save()) {
if ($request->filled('groups')) {
$user->groups()->sync($request->input('groups'));
@@ -496,7 +504,9 @@ class UsersController extends Controller
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
$user->customFill($request,Auth::user());
if ($user->save()) {
// Check if the request has groups passed and has a value, AND that the user us a superuser

View File

@@ -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();
}
}

View File

@@ -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()) {

View File

@@ -6,9 +6,11 @@ use App\Helpers\Helper;
use App\Http\Requests\CustomFieldRequest;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use App\Models\User;
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 +22,7 @@ use Illuminate\Http\Request;
*/
class CustomFieldsController extends Controller
{
/**
* Returns a view with a listing of custom fields.
*
@@ -28,12 +31,16 @@ 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);
if ($request->input('tab') == 1) {
// Users section, make sure to auto-create the first fieldset if so
CustomFieldset::firstOrCreate(['type' => Helper::$itemtypes_having_custom_fields[1]], ['name' => 'default']);
}
$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);
}
@@ -66,7 +73,7 @@ class CustomFieldsController extends Controller
public function create(Request $request)
{
$this->authorize('create', CustomField::class);
$fieldsets = CustomFieldset::get();
$fieldsets = CustomFieldset::where('type', Helper::$itemtypes_having_custom_fields[$request->get('tab')])->get();
return view('custom_fields.fields.edit', [
'predefinedFormats' => Helper::predefined_formats(),
@@ -110,8 +117,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')) {
@@ -124,14 +133,17 @@ class CustomFieldsController extends Controller
// Sync fields with fieldsets
$fieldset_array = $request->input('associate_fieldsets');
if ($request->has('associate_fieldsets') && (is_array($fieldset_array))) {
if ($request->get('tab') == 1) {
$fieldset_array = [CustomFieldset::firstOrCreate(['type' => User::class], ['name' => 'default'])->id => true];
}
if (($request->has('associate_fieldsets') || $request->get('tab') == 1) && (is_array($fieldset_array))) {
$field->fieldset()->sync(array_keys($fieldset_array));
} else {
$field->fieldset()->sync([]);
}
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()
@@ -184,12 +196,16 @@ class CustomFieldsController extends Controller
if ($field = CustomField::find($field_id)) {
$this->authorize('delete', $field);
if ($field->type == User::class) {
$field->fieldset()->detach(); // remove from 'default' group (and others, if they exist in the future!)
}
if (($field->fieldset) && ($field->fieldset->count() > 0)) {
return redirect()->back()->withErrors(['message' => 'Field is in-use']);
}
$type = $field->type;
$field->delete();
return redirect()->route("fields.index")
->with("success", trans('admin/custom_fields/message.field.delete.success'));
return redirect()->route('fields.index', ['tab' => array_search($type, Helper::$itemtypes_having_custom_fields)])
->with('success', trans('admin/custom_fields/message.field.delete.success'));
}
return redirect()->back()->withErrors(['message' => 'Field does not exist']);
@@ -290,7 +306,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'));

View File

@@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Models\AssetModel;
use App\Models\CustomField;
use App\Models\CustomFieldset;
@@ -38,7 +39,7 @@ class CustomFieldsetsController extends Controller
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.8]
*/
public function show($id)
public function show( $id)
{
$cfset = CustomFieldset::with('fields')
->where('id', '=', $id)->orderBy('id', 'ASC')->first();
@@ -46,7 +47,7 @@ class CustomFieldsetsController extends Controller
$this->authorize('view', $cfset);
if ($cfset) {
$custom_fields_list = ['' => 'Add New Field to Fieldset'] + CustomField::pluck('name', 'id')->toArray();
$custom_fields_list = ['' => 'Add New Field to Fieldset'] + CustomField::where('type', $cfset->type)->pluck('name', 'id')->toArray();
$maxid = 0;
foreach ($cfset->fields as $field) {
@@ -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);

View File

@@ -130,6 +130,9 @@ class UsersController extends Controller
// we have to invoke the
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
\Log::info("About to call customFill, in the 'store' controller!!!");
$user->customFill($request, Auth::user());
if ($user->save()) {
if ($request->filled('groups')) {
$user->groups()->sync($request->input('groups'));
@@ -321,7 +324,31 @@ class UsersController extends Controller
}
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id')));
$permissions_array = $request->input('permission');
// Strip out the superuser permission if the user isn't a superadmin
if (!Auth::user()->isSuperUser()) {
unset($permissions_array['superuser']);
$permissions_array['superuser'] = $orig_superuser;
}
$user->permissions = json_encode($permissions_array);
// Handle uploaded avatar
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
\Log::debug("calling custom fill from the UPDATE method!");
$user->customFill($request, Auth::user());
//\Log::debug(print_r($user, true));
// Was the user updated?
if ($user->save()) {
// Redirect to the user page
return redirect()->route('users.index')
->with('success', trans('admin/users/message.success.update'));
}
return redirect()->back()->withInput()->withErrors($user->getErrors());
}
/**

View File

@@ -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()

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -2,6 +2,7 @@
namespace App\Http\Transformers;
use App\Helpers\CustomFieldHelper;
use App\Helpers\Helper;
use App\Models\Asset;
use App\Models\Setting;
@@ -99,49 +100,7 @@ class AssetsTransformer
];
if (($asset->model) && ($asset->model->fieldset) && ($asset->model->fieldset->fields->count() > 0)) {
$fields_array = [];
foreach ($asset->model->fieldset->fields as $field) {
if ($field->isFieldDecryptable($asset->{$field->db_column})) {
$decrypted = Helper::gracefulDecrypt($field, $asset->{$field->db_column});
$value = (Gate::allows('assets.view.encrypted_custom_fields')) ? $decrypted : strtoupper(trans('admin/custom_fields/general.encrypted'));
if ($field->format == 'DATE'){
if (Gate::allows('assets.view.encrypted_custom_fields')){
$value = Helper::getFormattedDateObject($value, 'date', false);
} else {
$value = strtoupper(trans('admin/custom_fields/general.encrypted'));
}
}
$fields_array[$field->name] = [
'field' => e($field->db_column),
'value' => e($value),
'field_format' => $field->format,
'element' => $field->element,
];
} else {
$value = $asset->{$field->db_column};
if (($field->format == 'DATE') && (!is_null($value)) && ($value!='')){
$value = Helper::getFormattedDateObject($value, 'date', false);
}
$fields_array[$field->name] = [
'field' => e($field->db_column),
'value' => e($value),
'field_format' => $field->format,
'element' => $field->element,
];
}
$array['custom_fields'] = $fields_array;
}
} else {
$array['custom_fields'] = new \stdClass; // HACK to force generation of empty object instead of empty list
}
$array['custom_fields'] = CustomFieldHelper::transform($asset->model->fieldset,$asset);
$permissions_array['available_actions'] = [
'checkout' => ($asset->deleted_at=='' && Gate::allows('checkout', Asset::class)) ? true : false,

View File

@@ -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;
}

View File

@@ -2,7 +2,10 @@
namespace App\Http\Transformers;
use App\Helpers\CustomFieldHelper;
use App\Helpers\Helper;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection;
@@ -80,6 +83,8 @@ class UsersTransformer
'deleted_at' => ($user->deleted_at) ? Helper::getFormattedDateObject($user->deleted_at, 'datetime') : null,
];
$array['custom_fields'] = CustomFieldHelper::transform(CustomFieldset::where('type',User::class)->first(), $user);
$permissions_array['available_actions'] = [
'update' => (Gate::allows('update', User::class) && ($user->deleted_at == '')),
'delete' => $user->isDeletable(),

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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
*
@@ -201,40 +217,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();
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();

View File

@@ -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
*

View File

@@ -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] [<uberbrady@gmail.com>]
* @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] [<snipe@snipe.net>]
* @since [v3.4]
* @return string
* @since [v3.4]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
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] [<snipe@snipe.net>]
* @since [v3.4]
* @return bool
* @since [v3.4]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
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!
}
/**

View File

@@ -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] [<uberbrady@gmail.com>]
* @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

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Watson\Validating\ValidatingTrait;
class DefaultValuesForCustomFields extends Model
{
use HasFactory, ValidatingTrait;
protected $rules = [
'type' => '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);
}
}

View File

@@ -0,0 +1,150 @@
<?php
namespace App\Models\Traits;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use Illuminate\Support\Collection;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Event;
use App\Models\DefaultValuesForCustomFields;
/*********************************
* Trait HasCustomFields
* @package App\Models\Traits
*
* How to use: declare a PHP function getFieldset that will return your fieldset (not the ID, the actual set)
*
*/
trait HasCustomFields
{
protected static function bootHasCustomFields()
{
// https://tech.chrishardie.com/2022/define-fire-listen-custom-laravel-model-events-trait/
static::registerModelEvent('validating', function ($model, $event) {
// \Log::error("Uh, something happened? Something good, maybe?");
// \Log::error("model: $model, event: $event");
// \Log::error("WHATS MY NAME? " . HasCustomFields::class);
// dump(class_uses_recursive($model));
if (in_array(HasCustomFields::class, class_uses_recursive($model))) {
\Log::error("!!!!!!!!!!!!! YOU ARE USING THE TRAIT!");
self::augmentValidationRulesForCustomFields($model);
} else {
\Log::error("You aren't useing the trait so go away");
}
});
}
/***************
* @return CustomFieldset|null
*
* This function by default will use the "getFieldsetKey()" method to
* return the customFieldset (or null) for this particular item. If
* necessary, you can override this method if your getFieldsetKey()
* cannot respond to `->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 CustomFieldset::find($pivot);
}
return $pivot?->fieldset; //this is bonkers, why is this even firing?!
}
/**********************
* @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) {
$success = 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 {
$success = false;
continue; //may not be necessary? I'm not sure. I like the other way of doing this TODO
}
} else {
$this->{$field->db_column} = $request->input($field->db_column);
}
}
}
return $success;
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Models;
use App\Http\Traits\UniqueUndeletedTrait;
use App\Models\Traits\HasCustomFields;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Illuminate\Support\Facades\DB;
@@ -32,6 +33,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
use Notifiable;
use Presentable;
use Searchable;
use HasCustomFields;
protected $hidden = ['password', 'remember_token', 'permissions', 'reset_password_code', 'persist_code'];
protected $table = 'users';
@@ -137,6 +139,20 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
'manager' => ['first_name', 'last_name', 'username'],
];
public function getFieldsetKey(): object|int|null
{
// TODO/FIXME - that's hardcoded text, but what language should you use?! I don't know.
// also TODO - is this going to beat on the DB too hard?
return CustomFieldset::where('type', User::class)->first()?->id;
}
public static function getFieldsetUsers(int $fieldset_id): array
{
return [
'no_idea_what_id_to_put' => 'No idea what string to put?' // FIXME obvs.
];
}
/**
* Internally check the user permission for the given section
*

View File

@@ -2,8 +2,11 @@
namespace App\Presenters;
use App\Helpers\CustomFieldHelper;
use App\Models\Asset;
use App\Models\CustomField;
use Carbon\CarbonImmutable;
use App\Models\CustomFieldset;
use DateTime;
/**
@@ -299,25 +302,21 @@ 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(); // FIXME: d'oh! this is wrong. We just got fieldsets, above. Now we're getting fields?
// 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
foreach ($fields as $field) {
$layout[] = [
'field' => 'custom_fields.'.$field->db_column,
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => $field->name,
'formatter'=> 'customFieldsFormatter',
'escape' => true,
'class' => ($field->field_encrypted == '1') ? 'css-padlock' : '',
'visible' => ($field->show_in_listview == '1') ? true : false,
];
$layout[] = CustomFieldHelper::present($field);
}
$layout[] = [

View File

@@ -2,8 +2,14 @@
namespace App\Presenters;
use App\Helpers\CustomFieldHelper;
use App\Helpers\Helper;
use App\Models\Asset;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
@@ -386,6 +392,30 @@ class UserPresenter extends Presenter
],
];
// TODO - FIXME - this is all copy-pasta'ed from the AssetPresenter! <start>
//only get fieldsets that have fields
$fieldsets = CustomFieldset::where("type", User::class)->whereHas('fields')->get();
$ids = [];
foreach($fieldsets as $fieldset) {
if (count($fieldset->customizables()) > 0) { //only get fieldsets that are 'in use'
\Log::debug("Found a fieldset! It's: ".$fieldset->id);
$ids[] = $fieldset->id;
} else {
\Log::debug("Didn't find fieldset: ".$fieldset->id);
}
}
$fields = CustomField::whereHas('fieldset', function (Builder $query) use($ids) {
$query->whereIn('custom_fieldsets.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
foreach ($fields as $field) {
\Log::debug("iterating through fields!");
$layout[] = CustomFieldHelper::present($field);
}
return json_encode($layout);
}

View File

@@ -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')));

View File

@@ -1,5 +1,7 @@
<?php
use Illuminate\Http\Request;
/*
|--------------------------------------------------------------------------
| DO NOT EDIT THIS FILE DIRECTLY.
@@ -57,6 +59,9 @@ return [
*
* @link https://symfony.com/doc/current/deployment/proxies.html
*/
// 'headers' => 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,
];

View File

@@ -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();
}
];
});
}
}

View File

@@ -448,4 +448,13 @@ class AssetModelFactory extends Factory
];
});
}
public function complicated()
{
return $this->state(function () {
return [
'name' => 'Complicated fieldset'
];
})->for(CustomFieldSet::factory()->complicated(), 'fieldset');
}
}

View File

@@ -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'
];
});
}
}

View File

@@ -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');
}
}

View File

@@ -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

View File

@@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use App\Models\CustomField;
use App\Models\Asset;
class AddTypeToCustomFields extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('custom_fields', function (Blueprint $table) {
//
$table->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');
});
}
}

View File

@@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use App\Models\CustomFieldset;
use App\Models\Asset;
class AddTypeToCustomFieldsets extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('custom_fieldsets', function (Blueprint $table) {
//
$table->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');
});
}
}

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class GeneralizeDefaultValuesForCustomFields extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::rename('models_custom_fields', 'default_values_for_custom_fields');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::rename('default_values_for_custom_fields', 'models_custom_fields');
}
}

View File

@@ -0,0 +1,44 @@
<?php
use App\Models\Asset;
use App\Models\DefaultValuesForCustomFields;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddTypeColumnToDefaultValuesForCustomFields extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('default_values_for_custom_fields', function (Blueprint $table) {
$table->renameColumn('asset_model_id','item_pivot_id'); //this one works. okay. that's someting.
});
Schema::table('default_values_for_custom_fields', function (Blueprint $table) {
$table->string('type')->nullable();
});
DefaultValuesForCustomFields::query()->update(['type' => Asset::class]);
Schema::table('default_values_for_custom_fields', function (Blueprint $table) {
//$table->renameColumn('asset_model_id','item_pivot_id'); //this one works. okay. that's someting.
$table->string('type')->nullable(false)->change();
});
}
/**
* 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');
});
}
}

View File

@@ -30,6 +30,8 @@
@endif
@csrf
<input type="hidden" name="tab" value="{{ Request::query('tab') }}" />
<div class="row">
<div class="col-md-12">
<div class="box box-default">
@@ -151,12 +153,16 @@
<!-- Auto-Add to Future Fieldsets -->
<div class="form-group {{ $errors->has('auto_add_to_fieldsets') ? ' has-error' : '' }}"
id="auto_add_to_fieldsets">
@if (Request::query('tab') != 1)
<div class="col-md-9 col-md-offset-3">
<label class="form-control">
<input type="checkbox" name="auto_add_to_fieldsets" aria-label="auto_add_to_fieldsets" value="1"{{ (old('auto_add_to_fieldsets') || $field->auto_add_to_fieldsets) ? ' checked="checked"' : '' }}>
{{ trans('admin/custom_fields/general.auto_add_to_fieldsets') }}
</label>
</div>
@endif
<!-- Show in list view -->
<div class="col-md-9 col-md-offset-3">
@@ -208,7 +214,7 @@
</div>
@if ($fieldsets->count() > 0)
@if ($fieldsets->count() > 0 && Request::query('tab') != 1)
<!-- begin fieldset columns -->
<div class="col-md-4">

View File

@@ -12,6 +12,7 @@
@section('inputFields')
@include ('partials.forms.edit.name', ['translated_name' => trans('general.name')])
<input type="hidden" name="tab" value="{{ Request::query('tab') }}" />
@stop

View File

@@ -13,6 +13,29 @@
@section('content')
@can('view', \App\Models\CustomFieldset::class)
<div class="row">
<div calss="col-md-12">
<div class="box box-default">
<div class="box-body">
<div class="nav-tabs-custom">
<ul class="nav nav-tabs">
{{-- TODO - generalize this so it's less 'hardcoded' --}}
<li {!! !Request::query('tab') ? 'class="active"': '' !!}><a
href="{{ route("fields.index",["tab" => 0]) }}">Asset Custom Fields</a></li>
<li {!! Request::query('tab') == 1 ? 'class="active"': '' !!}><a
href="{{ route("fields.index",["tab" => 1]) }}">Users</a></li>
<li {!! Request::query('tab') == 2 ? 'class="active"': '' !!}><a
href="{{ route("fields.index",["tab" => 2]) }}">Accessories</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
{{-- Do not show fieldsets for Users' customf ields --}}
@if(Request::query('tab') != 1)
<div class="row">
<div class="col-md-12">
<div class="box box-default">
@@ -21,7 +44,9 @@
<h2 class="box-title">{{ trans('admin/custom_fields/general.fieldsets') }}</h2>
<div class="box-tools pull-right">
@can('create', \App\Models\CustomFieldset::class)
<a href="{{ route('fieldsets.create') }}" class="btn btn-sm btn-primary" data-tooltip="true" title="{{ trans('admin/custom_fields/general.create_fieldset_title') }}">{{ trans('admin/custom_fields/general.create_fieldset') }}</a>
<a href="{{ route('fieldsets.create',['tab' => Request::query('tab',0)]) }}" class="btn btn-sm btn-primary"
data-tooltip="true"
title="{{ trans('admin/custom_fields/general.create_fieldset_title') }}">{{ trans('admin/custom_fields/general.create_fieldset') }}</a>
@endcan
</div>
</div><!-- /.box-header -->
@@ -47,7 +72,7 @@
<tr>
<th>{{ trans('general.name') }}</th>
<th>{{ trans('admin/custom_fields/general.qty_fields') }}</th>
<th>{{ trans('admin/custom_fields/general.used_by_models') }}</th>
<th>{{ trans('admin/custom_fields/general.used_by_models') }}{{-- FIXME --}}</th>
<th>{{ trans('table.actions') }}</th>
</tr>
</thead>
@@ -63,9 +88,9 @@
{{ $fieldset->fields->count() }}
</td>
<td>
@foreach($fieldset->models as $model)
<a href="{{ route('models.show', $model->id) }}" class="label label-default">{{ $model->name }}{{ ($model->model_number) ? ' ('.$model->model_number.')' : '' }}</a>
@foreach($fieldset->customizables() as $url => $name)
<a href="{{ $url }}" class="label label-default">{{ $name }}</a>
{{-- get_class($customizable) }}: {{ $customizable->name<br /> --}}
@endforeach
</td>
<td>
@@ -88,10 +113,13 @@
@can('delete', $fieldset)
{{ Form::open(['route' => array('fieldsets.destroy', $fieldset->id), 'method' => 'delete','style' => 'display:inline-block']) }}
@if($fieldset->models->count() > 0)
<button type="submit" class="btn btn-danger btn-sm disabled" data-tooltip="true" title="{{ trans('general.cannot_be_deleted') }}" disabled><i class="fas fa-trash"></i></button>
@if(count($fieldset->customizables()) > 0 /* TODO - hate 'customizables' */)
<button type="submit" class="btn btn-danger btn-sm disabled" data-tooltip="true"
title="{{ trans('general.cannot_be_deleted') }}" disabled><i class="fas fa-trash"></i>
</button>
@else
<button type="submit" class="btn btn-danger btn-sm" data-tooltip="true" title="{{ trans('general.delete') }}"><i class="fas fa-trash"></i></button>
<button type="submit" class="btn btn-danger btn-sm" data-tooltip="true"
title="{{ trans('general.delete') }}"><i class="fas fa-trash"></i></button>
@endif
{{ Form::close() }}
@endcan
@@ -109,6 +137,7 @@
</div> <!-- .row-->
@endif
@endcan
@can('view', \App\Models\CustomField::class)
<div class="row">
@@ -118,7 +147,9 @@
<h2 class="box-title">{{ trans('admin/custom_fields/general.custom_fields') }}</h2>
<div class="box-tools pull-right">
@can('create', \App\Models\CustomField::class)
<a href="{{ route('fields.create') }}" class="btn btn-sm btn-primary" data-tooltip="true" title="{{ trans('admin/custom_fields/general.create_field_title') }}">{{ trans('admin/custom_fields/general.create_field') }}</a>
<a href="{{ route('fields.create', ['tab' => Request::query('tab',0)]) }}" class="btn btn-sm btn-primary"
data-tooltip="true"
title="{{ trans('admin/custom_fields/general.create_field_title') }}">{{ trans('admin/custom_fields/general.create_field') }}</a>
@endcan
</div>
@@ -195,7 +226,8 @@
<nobr>
{{ Form::open(array('route' => array('fields.destroy', $field->id), 'method' => 'delete', 'style' => 'display:inline-block')) }}
@can('update', $field)
<a href="{{ route('fields.edit', $field->id) }}" class="btn btn-warning btn-sm" data-tooltip="true" title="{{ trans('general.update') }}">
<a href="{{ route('fields.edit', $field->id) }}?tab={{ array_search($field->type, Helper::$itemtypes_having_custom_fields) }}"
class="btn btn-warning btn-sm" data-tooltip="true" title="{{ trans('general.update') }}">
<i class="fas fa-pencil-alt" aria-hidden="true"></i>
<span class="sr-only">{{ trans('button.edit') }}</span>
</a>
@@ -203,7 +235,7 @@
@can('delete', $field)
@if($field->fieldset->count()>0)
@if($field->fieldset->count()>0 && Request::query('tab') != 1 )
<button type="submit" class="btn btn-danger btn-sm disabled" data-tooltip="true" title="{{ trans('general.cannot_be_deleted') }}" disabled>
<i class="fas fa-trash" aria-hidden="true"></i>
<span class="sr-only">{{ trans('button.delete') }}</span></button>

View File

@@ -75,20 +75,17 @@
<div id='custom_fields_content'>
<!-- Custom Fields -->
@if ($item->model && $item->model->fieldset)
<?php $model = $item->model; ?>
@endif
@if (Request::old('model_id'))
@php
$model = \App\Models\AssetModel::find(old('model_id'));
$item->model = \App\Models\AssetModel::find(old('model_id'));
@endphp
@elseif (isset($selected_model))
@php
$model = $selected_model;
$item->model_id = $selected_model;
@endphp
@endif
@if (isset($model) && $model)
@include("models/custom_fields_form",["model" => $model])
@if ($item->getFieldset())
@include("models/custom_fields_form",["item" => $item])
@endif
</div>

View File

@@ -418,63 +418,8 @@
</div>
</div>
@if (($asset->model) && ($asset->model->fieldset))
@foreach($asset->model->fieldset->fields as $field)
<div class="row">
<div class="col-md-2">
<strong>
{{ $field->name }}
</strong>
</div>
<div class="col-md-6{{ (($field->format=='URL') && ($asset->{$field->db_column_name()}!='')) ? ' ellipsis': '' }}">
@if (($field->field_encrypted=='1') && ($asset->{$field->db_column_name()}!=''))
<i class="fas fa-lock" data-tooltip="true" data-placement="top" title="{{ trans('admin/custom_fields/general.value_encrypted') }}" onclick="showHideEncValue(this)" id="text-{{ $field->id }}"></i>
@endif
@if ($field->isFieldDecryptable($asset->{$field->db_column_name()} ))
@can('assets.view.encrypted_custom_fields')
@php
$fieldSize=strlen(Helper::gracefulDecrypt($field, $asset->{$field->db_column_name()}))
@endphp
@if ($fieldSize>0)
<span id="text-{{ $field->id }}-to-hide">{{ str_repeat('*', $fieldSize) }}</span>
<span class="js-copy-{{ $field->id }}" id="text-{{ $field->id }}-to-show" style="font-size: 0px;">
@if (($field->format=='URL') && ($asset->{$field->db_column_name()}!=''))
<a href="{{ Helper::gracefulDecrypt($field, $asset->{$field->db_column_name()}) }}" target="_new">{{ Helper::gracefulDecrypt($field, $asset->{$field->db_column_name()}) }}</a>
@elseif (($field->format=='DATE') && ($asset->{$field->db_column_name()}!=''))
{{ \App\Helpers\Helper::gracefulDecrypt($field, \App\Helpers\Helper::getFormattedDateObject($asset->{$field->db_column_name()}, 'date', false)) }}
@else
{{ Helper::gracefulDecrypt($field, $asset->{$field->db_column_name()}) }}
@endif
</span>
<i class="fa-regular fa-clipboard js-copy-link" data-clipboard-target=".js-copy-{{ $field->id }}" aria-hidden="true" data-tooltip="true" data-placement="top" title="{{ trans('general.copy_to_clipboard') }}">
<span class="sr-only">{{ trans('general.copy_to_clipboard') }}</span>
</i>
@endif
@else
{{ strtoupper(trans('admin/custom_fields/general.encrypted')) }}
@endcan
@else
@if (($field->format=='BOOLEAN') && ($asset->{$field->db_column_name()}!=''))
{!! ($asset->{$field->db_column_name()} == 1) ? "<span class='fas fa-check-circle' style='color:green' />" : "<span class='fas fa-times-circle' style='color:red' />" !!}
@elseif (($field->format=='URL') && ($asset->{$field->db_column_name()}!=''))
<a href="{{ $asset->{$field->db_column_name()} }}" target="_new">{{ $asset->{$field->db_column_name()} }}</a>
@elseif (($field->format=='DATE') && ($asset->{$field->db_column_name()}!=''))
{{ \App\Helpers\Helper::getFormattedDateObject($asset->{$field->db_column_name()}, 'date', false) }}
@else
{!! nl2br(e($asset->{$field->db_column_name()})) !!}
@endif
@endif
@if ($asset->{$field->db_column_name()}=='')
&nbsp;
@endif
</div>
</div>
@endforeach
@endif
@include('partials.custom-fields-view', ['item' => $asset,'width' => 2])
@if ($asset->purchase_date)

View File

@@ -1,5 +1,28 @@
@if (($model) && ($model->fieldset))
@foreach($model->fieldset->fields AS $field)
{{--
Okay, now how am I going to work *this* out. I think it's less bad than I think.
I think we can pass the $asset or the $user to this partial.
This partial can be aware of the HasCustomFields trait, and call getFieldset() to get the appropriate fieldset; that's good.
But we also need the 'discriminator' so that we can use defaultValuesForCustomFields, right?
Well, that should be easy enough, right? Just call getFieldsetKey(), right? Or maybe we don't even have to do that -
We can call $item->getDefaultValue($field), right?
So the old way - already on this page - is:
$item->defaultValue($field))
And we just do a simple 'replace' to make it be:
$item->defaultValue($field)
--}}
@if ($item->getFieldset())
@foreach($item->getFieldset()->fields AS $field)
<div class="form-group{{ $errors->has($field->db_column_name()) ? ' has-error' : '' }}">
<label for="{{ $field->db_column_name() }}" class="col-md-3 control-label">{{ $field->name }} </label>
<div class="col-md-7 col-sm-12{{ ($field->pivot->required=='1') ? ' required' : '' }}">
@@ -12,14 +35,14 @@
Request::old($field->db_column_name(),(isset($item) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $field->defaultValue($model->id))), ['class'=>'format select2 form-control']) }}
@elseif ($field->element=='textarea')
<textarea class="col-md-6 form-control" id="{{ $field->db_column_name() }}" name="{{ $field->db_column_name() }}">{{ Request::old($field->db_column_name(),(isset($item) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $field->defaultValue($model->id))) }}</textarea>
<textarea class="col-md-6 form-control" id="{{ $field->db_column_name() }}" name="{{ $field->db_column_name() }}">{{ Request::old($field->db_column_name(),(isset($item) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $item->defaultValue($field))) }}</textarea>
@elseif ($field->element=='checkbox')
<!-- Checkboxes -->
@foreach ($field->formatFieldValuesAsArray() as $key => $value)
<div>
<label class="form-control">
<input type="checkbox" value="{{ $value }}" name="{{ $field->db_column_name() }}[]" {{ isset($item) ? (in_array($value, array_map('trim', explode(',', $item->{$field->db_column_name()}))) ? ' checked="checked"' : '') : (Request::old($field->db_column_name()) != '' ? ' checked="checked"' : (in_array($key, array_map('trim', explode(',', $field->defaultValue($model->id)))) ? ' checked="checked"' : '')) }}>
<input type="checkbox" value="{{ $value }}" name="{{ $field->db_column_name() }}[]" {{ isset($item) ? (in_array($value, array_map('trim', explode(',', $item->{$field->db_column_name()}))) ? ' checked="checked"' : '') : (Request::old($field->db_column_name()) != '' ? ' checked="checked"' : (in_array($key, array_map('trim', explode(',', $item->defaultValue($field)))) ? ' checked="checked"' : '')) }}>
{{ $value }}
</label>
</div>
@@ -30,7 +53,7 @@
<div>
<label class="form-control">
<input type="radio" value="{{ $value }}" name="{{ $field->db_column_name() }}" {{ isset($item) ? ($item->{$field->db_column_name()} == $value ? ' checked="checked"' : '') : (Request::old($field->db_column_name()) != '' ? ' checked="checked"' : (in_array($value, explode(', ', $field->defaultValue($model->id))) ? ' checked="checked"' : '')) }}>
<input type="radio" value="{{ $value }}" name="{{ $field->db_column_name() }}" {{ isset($item) ? ($item->{$field->db_column_name()} == $value ? ' checked="checked"' : '') : (Request::old($field->db_column_name()) != '' ? ' checked="checked"' : (in_array($value, explode(', ', $item->defaultValue($field))) ? ' checked="checked"' : '')) }}>
{{ $value }}
</label>
</div>
@@ -46,7 +69,7 @@
<div class="input-group col-md-5" style="padding-left: 0px;">
<div class="input-group date" data-provide="datepicker" data-date-format="yyyy-mm-dd" data-autoclose="true" data-date-clear-btn="true">
<input type="text" class="form-control" placeholder="{{ trans('general.select_date') }}" name="{{ $field->db_column_name() }}" id="{{ $field->db_column_name() }}" readonly value="{{ old($field->db_column_name(),(isset($item) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $field->defaultValue($model->id))) }}" style="background-color:inherit">
<input type="text" class="form-control" placeholder="{{ trans('general.select_date') }}" name="{{ $field->db_column_name() }}" id="{{ $field->db_column_name() }}" readonly value="{{ old($field->db_column_name(),(isset($item) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $item->defaultValue($field))) }}" style="background-color:inherit">
<span class="input-group-addon"><i class="fas fa-calendar" aria-hidden="true"></i></span>
</div>
</div>
@@ -54,7 +77,7 @@
@else
@if (($field->field_encrypted=='0') || (Gate::allows('assets.view.encrypted_custom_fields')))
<input type="text" value="{{ Request::old($field->db_column_name(),(isset($item) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $field->defaultValue($model->id))) }}" id="{{ $field->db_column_name() }}" class="form-control" name="{{ $field->db_column_name() }}" placeholder="Enter {{ strtolower($field->format) }} text">
<input type="text" value="{{ Request::old($field->db_column_name(),(isset($item) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $item->defaultValue($field))) }}" id="{{ $field->db_column_name() }}" class="form-control" name="{{ $field->db_column_name() }}" placeholder="Enter {{ strtolower($field->format) }} text">
@else
<input type="text" value="{{ strtoupper(trans('admin/custom_fields/general.encrypted')) }}" class="form-control disabled" disabled>
@endif

View File

@@ -0,0 +1,47 @@
{{-- FIXME - this doesn't work for Assets (crap!) --}}
@if ($item->getFieldset())
@foreach($item->getFieldset()->fields as $field)
<div class="row">
<div class="col-md-{{ $width }}">
<strong>
{{ $field->name }}
</strong>
</div>
<div class="col-md-{{ 12 - $width }}{{ (($field->format=='URL') && ($item->{$field->db_column_name()}!='')) ? ' ellipsis': '' }}">
@if ($field->field_encrypted=='1')
<i class="fas fa-lock" data-tooltip="true" data-placement="top" title="{{ trans('admin/custom_fields/general.value_encrypted') }}"></i>
@endif
@if ($field->isFieldDecryptable($item->{$field->db_column_name()} ))
@can('assets.view.encrypted_custom_fields')
@if (($field->format=='URL') && ($item->{$field->db_column_name()}!=''))
<a href="{{ Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) }}" target="_new">{{ Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) }}</a>
@elseif (($field->format=='DATE') && ($item->{$field->db_column_name()}!=''))
{{ \App\Helpers\Helper::gracefulDecrypt($field, \App\Helpers\Helper::getFormattedDateObject($item->{$field->db_column_name()}, 'date', false)) }}
@else
{{ Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) }}
@endif
@else
{{ strtoupper(trans('admin/custom_fields/general.encrypted')) }}
@endcan
@else
@if (($field->format=='BOOLEAN') && ($item->{$field->db_column_name()}!=''))
{!! ($item->{$field->db_column_name()} == 1) ? "<span class='fas fa-check-circle' style='color:green' />" : "<span class='fas fa-times-circle' style='color:red' />" !!}
@elseif (($field->format=='URL') && ($item->{$field->db_column_name()}!=''))
<a href="{{ $item->{$field->db_column_name()} }}" target="_new">{{ $item->{$field->db_column_name()} }}</a>
@elseif (($field->format=='DATE') && ($item->{$field->db_column_name()}!=''))
{{ \App\Helpers\Helper::getFormattedDateObject($item->{$field->db_column_name()}, 'date', false) }}
@else
{!! nl2br(e($item->{$field->db_column_name()})) !!}
@endif
@endif
@if ($item->{$field->db_column_name()}=='')
&nbsp;
@endif
</div>
</div>
@endforeach
@endif

View File

@@ -279,6 +279,15 @@
@include ('partials.forms.edit.image-upload', ['fieldname' => 'avatar', 'image_path' => app('users_upload_path')])
{{-- FIXME - copypasta from hardware/edit.blade.php <start> --}}
<div id='custom_fields_content'>
<!-- Custom Fields -->
@if ($user->getFieldset())
@include("models/custom_fields_form",["item" => $user])
@endif
</div>
{{-- FIXME - copypasts from hardware/edit.blade.php <end> --}}
<!-- begin optional disclosure arrow stuff -->
<div class="form-group">

View File

@@ -677,7 +677,60 @@
</div>
</div><!--/.row-->
@endif
</div> <!--/end striped container-->
{{-- FIXME - copypasta from hardware/view.blade.php! <start> --}}
@if (($user->getFieldsetKey()) && (App\Models\CustomFieldset::find($user->getFieldsetKey())))
@foreach(App\Models\CustomFieldset::find($user->getFieldsetKey())->fields as $field)
<div class="row">
<div class="col-md-3">
<strong>
{{ $field->name }}
</strong>
</div>
<div class="col-md-9{{ (($field->format=='URL') && ($user->{$field->db_column_name()}!='')) ? ' ellipsis': '' }}">
@if ($field->field_encrypted=='1')
<i class="fas fa-lock" data-tooltip="true" data-placement="top"
title="{{ trans('admin/custom_fields/general.value_encrypted') }}"></i>
@endif
@if ($field->isFieldDecryptable($user->{$field->db_column_name()} ))
@can('assets.view.encrypted_custom_fields')
@if (($field->format=='URL') && ($user->{$field->db_column_name()}!=''))
<a href="{{ Helper::gracefulDecrypt($field, $user->{$field->db_column_name()}) }}"
target="_new">{{ Helper::gracefulDecrypt($field, $user->{$field->db_column_name()}) }}</a>
@elseif (($field->format=='DATE') && ($user->{$field->db_column_name()}!=''))
{{ \App\Helpers\Helper::gracefulDecrypt($field, \App\Helpers\Helper::getFormattedDateObject($user->{$field->db_column_name()}, 'date', false)) }}
@else
{{ Helper::gracefulDecrypt($field, $user->{$field->db_column_name()}) }}
@endif
@else
{{ strtoupper(trans('admin/custom_fields/general.encrypted')) }}
@endcan
@else
@if (($field->format=='BOOLEAN') && ($user->{$field->db_column_name()}!=''))
{!! ($user->{$field->db_column_name()} == 1) ? "<span class='fas fa-check-circle' style='color:green' />" : "<span class='fas fa-times-circle' style='color:red' />" !!}
@elseif (($field->format=='URL') && ($user->{$field->db_column_name()}!=''))
<a href="{{ $user->{$field->db_column_name()} }}"
target="_new">{{ $user->{$field->db_column_name()} }}</a>
@elseif (($field->format=='DATE') && ($user->{$field->db_column_name()}!=''))
{{ \App\Helpers\Helper::getFormattedDateObject($user->{$field->db_column_name()}, 'date', false) }}
@else
{!! nl2br(e($user->{$field->db_column_name()})) !!}
@endif
@endif
@if ($user->{$field->db_column_name()}=='')
&nbsp;
@endif
</div>
</div>
@endforeach
@endif
{{-- FIXME <end> copypasta from hardware/view.blade.php --}}
@include('partials.custom-fields-view',['item' => $user,'width' => 3])
</div> <!--/end striped container-->
</div> <!-- end col-md-9 -->
</div> <!--/.row-->
</div><!-- /.tab-pane -->

View File

@@ -18,12 +18,13 @@ class UpdateAssetTest extends TestCase
$asset = Asset::factory()->hasEncryptedCustomField($field)->create();
$superuser = User::factory()->superuser()->create();
$this->actingAsForApi($superuser)
$results = $this->actingAsForApi($superuser)
->patchJson(route('api.assets.update', $asset->id), [
$field->db_column_name() => 'This is encrypted field'
])
->assertStatusMessageIs('success')
->assertOk();
\Log::error(print_r($results, true));
$asset->refresh();
$this->assertEquals('This is encrypted field', Crypt::decrypt($asset->{$field->db_column_name()}));

View File

@@ -0,0 +1,83 @@
<?php
namespace Tests\Unit;
use App\Models\Asset;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use App\Models\Traits\HasCustomFields;
use Illuminate\Support\Collection;
use Tests\Support\InitializesSettings;
use Tests\TestCase;
use Illuminate\Support\Facades\Schema;
class HasCustomFieldsTraitTest extends TestCase
{
use InitializesSettings;
//seems bonkers, but Assets needs it? (for currency calculation?)
public function testAssetSchema()
{
$asset = Asset::factory()->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()
{
//FIXME - this is in the wrong place and it might just be genuinley wrong?
$this->markTestIncomplete();
$asset = Asset::factory()->withComplicatedCustomFields()->make();
$response = $this->postJson('/api/v1/hardware', [
]);
$response->assertStatus(200);
}
}