Work on adding additional objectclasses to an entry

This commit is contained in:
Deon George 2025-02-02 09:22:42 +11:00
parent 3a4b0bfe05
commit d61685a5b2
9 changed files with 244 additions and 51 deletions

View File

@ -442,6 +442,16 @@ final class ObjectClass extends Base
return $this->type; return $this->type;
} }
/**
* Return if this objectclass is auxiliary
*
* @return bool
*/
public function isAuxiliary(): bool
{
return $this->type === Server::OC_AUXILIARY;
}
/** /**
* Determine if an array is listed in the may_force attrs * Determine if an array is listed in the may_force attrs
*/ */

View File

@ -68,6 +68,28 @@ class HomeController extends Controller
->with('page_actions',$page_actions); ->with('page_actions',$page_actions);
} }
/**
* Render a new attribute view
*
* @param Request $request
* @param string $id
* @return \Closure|\Illuminate\Contracts\View\View|string
*/
public function entry_attr_add(Request $request,string $id)
{
$xx = new \stdClass();
$xx->index = 0;
$x = $request->noheader
? (string)view(sprintf('components.attribute.widget.%s',$id))
->with('o',new Attribute($id,[]))
->with('value',$request->value)
->with('loop',$xx)
: (new AttributeType(new Attribute($id,[]),TRUE))->render();
return $x;
}
public function entry_export(Request $request,string $id) public function entry_export(Request $request,string $id)
{ {
$dn = Crypt::decryptString($id); $dn = Crypt::decryptString($id);
@ -84,10 +106,33 @@ class HomeController extends Controller
->with('result',new LDIFExport($result)); ->with('result',new LDIFExport($result));
} }
public function entry_newattr(string $id) /**
* Render an available list of objectclasses for an Entry
*
* @param string $id
* @return mixed
*/
public function entry_objectclass_add(string $id)
{ {
$x = new AttributeType(new Attribute($id,[]),TRUE); $dn = Crypt::decryptString($id);
return $x->render(); $o = config('server')->fetch($dn);
$ocs = $o->getObject('objectclass')
->structural
->map(fn($item)=>$item->getParents())
->flatten()
->merge(
config('server')->schema('objectclasses')
->filter(fn($item)=>$item->isAuxiliary())
)
->sortBy(fn($item)=>$item->name);
return $ocs->groupBy(fn($item)=>$item->isStructural())
->map(fn($item,$key) =>
[
'text' => sprintf('%s Object Class',$key ? 'Structural' : 'Auxiliary'),
'children' => $item->map(fn($item)=>['id'=>$item->name,'text'=>$item->name]),
]);
} }
public function entry_password_check(Request $request) public function entry_password_check(Request $request)
@ -126,20 +171,22 @@ class HomeController extends Controller
$o->{$key} = array_filter($value,fn($item)=>! is_null($item)); $o->{$key} = array_filter($value,fn($item)=>! is_null($item));
// We need to process and encrypt the password // We need to process and encrypt the password
$passwords = []; if ($request->userpassword) {
foreach ($request->userpassword as $key => $value) { $passwords = [];
// If the password is still the MD5 of the old password, then it hasnt changed foreach ($request->userpassword as $key => $value) {
if (($old=Arr::get($o->userpassword,$key)) && ($value === md5($old))) { // If the password is still the MD5 of the old password, then it hasnt changed
array_push($passwords,$old); if (($old=Arr::get($o->userpassword,$key)) && ($value === md5($old))) {
continue; array_push($passwords,$old);
} continue;
}
if ($value) { if ($value) {
$type = Arr::get($request->userpassword_hash,$key); $type = Arr::get($request->userpassword_hash,$key);
array_push($passwords,Attribute\Password::hash_id($type)->encode($value)); array_push($passwords,Attribute\Password::hash_id($type)->encode($value));
}
} }
$o->userpassword = $passwords;
} }
$o->userpassword = $passwords;
if (! $o->getDirty()) if (! $o->getDirty())
return back() return back()
@ -294,10 +341,8 @@ class HomeController extends Controller
*/ */
public function schema_frame(Request $request) public function schema_frame(Request $request)
{ {
$s = config('server');
// If an invalid key, we'll 404 // If an invalid key, we'll 404
if ($request->type && $request->key && ($s->schema($request->type)->has($request->key) === FALSE)) if ($request->type && $request->key && (! config('server')->schema($request->type)->has($request->key)))
abort(404); abort(404);
return view('frames.schema') return view('frames.schema')

View File

@ -290,4 +290,9 @@ select2-container--bootstrap-5 .select2-selection--multiple .select2-selection__
.select2-container--bootstrap-5 .select2-selection--single .select2-selection__rendered .select2-selection__placeholder { .select2-container--bootstrap-5 .select2-selection--single .select2-selection__rendered .select2-selection__placeholder {
line-height: 1.0; line-height: 1.0;
font-size: 90%; font-size: 90%;
}
.select2-container--bootstrap-5 .select2-dropdown .select2-results__options .select2-results__option[role=group] .select2-results__group {
color: #212529;
font-weight: 800;
} }

View File

@ -2,14 +2,7 @@
<x-attribute.layout :edit="$edit" :new="$new" :o="$o"> <x-attribute.layout :edit="$edit" :new="$new" :o="$o">
@foreach (old($o->name_lc,$o->values) as $value) @foreach (old($o->name_lc,$o->values) as $value)
@if ($edit && ($value === NULL || (! $o->isStructural($value)))) @if ($edit && ($value === NULL || (! $o->isStructural($value))))
<div class="input-group has-validation"> <x-attribute.widget.objectclass :o="$o" :edit="$edit" :new="$new" :loop="$loop" :value="$value"/>
<input type="text" @class(['form-control','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$loop->index)),'mb-1','border-focus'=>$o->values->contains($value)]) name="{{ $o->name_lc }}[]" value="{{ $value }}" placeholder="{{ ! is_null($x=Arr::get($o->values,$loop->index)) ? $x : '['.__('NEW').']' }}" @readonly(true)>
<div class="invalid-feedback pb-2">
@if($e)
{{ join('|',$e) }}
@endif
</div>
</div>
@else @else
{{ $value }} {{ $value }}
@if ($o->isStructural($value)) @if ($o->isStructural($value))

View File

@ -0,0 +1,19 @@
<div class="input-group has-validation">
<!-- @todo Have an "x" to remove the entry, we need an event to process the removal, removing any attribute values along the way -->
<input type="text" @class(['form-control','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$loop->index)),'mb-1','border-focus'=>$o->values->contains($value)]) name="{{ $o->name_lc }}[]" value="{{ $value }}" placeholder="{{ Arr::get($o->values,$loop->index,'['.__('NEW').']') }}" @readonly(true)>
<div class="invalid-feedback pb-2">
@if($e)
{{ join('|',$e) }}
@endif
</div>
</div>
<span class="input-group-delete"><i class="fas fa-fw fa-xmark"></i></span>
<style>
.input-group-delete {
float: right;
position: relative;
top: -32px;
right: 10px;
}
</style>

View File

@ -1,22 +1,138 @@
@php($clone=FALSE)
@if($o->is_rdn) @if($o->is_rdn)
<span class="btn btn-sm btn-outline-focus mt-3"><i class="fas fa-fw fa-exchange"></i> @lang('Rename')</span> <span class="btn btn-sm btn-outline-focus mt-3"><i class="fas fa-fw fa-exchange"></i> @lang('Rename')</span>
@elseif($edit && $o->can_addvalues) @elseif($edit && $o->can_addvalues)
<span class="p-0 m-0"> <span class="p-0 m-0">
<span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name_lc }}"><i class="fas fa-fw fa-plus"></i> @lang('Add Value')</span> @switch(get_class($o))
</span> @case('App\Classes\LDAP\Attribute\Binary\JpegPhoto')
@endif <span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name_lc }}"><i class="fas fa-fw fa-plus"></i> @lang('Upload JpegPhoto')</span>
@section('page-scripts') @break
@if($edit && $o->can_addvalues)
<script type="text/javascript"> @case('App\Classes\LDAP\Attribute\ObjectClass')
$(document).ready(function() { <button type="button" @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) data-bs-toggle="modal" data-bs-target="#new_objectclass-modal"><i class="fas fa-fw fa-plus"></i> @lang('Add Objectclass')</button>
// Create a new entry when Add Value clicked
$('#{{ $o->name_lc }}.addable').click(function (item) { <!-- NEW OBJECT CLASS -->
var cln = $(this).parent().parent().find('input:last').parent().clone(); <div class="modal fade" id="new_objectclass-modal" tabindex="-1" aria-labelledby="new_objectclass-label" aria-hidden="true" data-bs-backdrop="static">
cln.find('input:last').attr('value','').attr('placeholder', '[@lang('NEW')]'); <div class="modal-dialog modal-lg modal-fullscreen-lg-down">
cln.appendTo('#' + item.currentTarget.id) <div class="modal-content">
}); <div class="modal-header">
}); <h1 class="modal-title fs-5" id="new_objectclass-label">New Object Class</h1>
</script> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
@endif </div>
@append
<div class="modal-body">
<x-form.select id="newoc" label="Select from..."/>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-primary" data-bs-dismiss="modal">Next</button>
{{--
<button type="button" class="btn btn-sm btn-primary" data-bs-dismiss="modal"><i class="fas fa-fw fa-spinner fa-spin d-none"></i> Next</button>
--}}
</div>
</div>
</div>
</div>
@section('page-scripts')
<script type="text/javascript">
$(document).ready(function() {
var added_oc = []; // Object classes being added to this entry
// Show our ObjectClass modal so that we can add more objectclasses
$('#new_objectclass-modal').on('shown.bs.modal',function() {
$.ajax({
type: 'POST',
success: function(data) {
$('select#newoc').select2({
dropdownParent: $('#new_objectclass-modal'),
theme: 'bootstrap-5',
allowClear: true,
multiple: true,
data: data,
});
},
error: function(e) {
if (e.status != 412)
alert('That didnt work? Please try again....');
},
url: '{{ url('entry/objectclass/add') }}/'+dn,
cache: false
});
})
// When the ObjectClass modal is closed, process what was selected
$('#new_objectclass-modal').on('hide.bs.modal',function() {
var c = {{ $o->values->count() }}; // @todo do we need this?
var newadded = $('select#newoc').val();
// If nothing selected, we dont have anything to do
if (! newadded.length)
return;
// Find out what was selected, and add them
newadded.forEach(function (item) {
if (added_oc.indexOf(item) !== -1)
return;
$.ajax({
type: 'GET',
beforeSend: function() {},
success: function(data) {
console.log(data);
$('#{{ $o->name_lc }}').append(data);
console.log('set to:'+item);
},
error: function(e) {
if (e.status != 412)
alert('That didnt work? Please try again....');
},
url: '{{ url('entry/attr/add',[$o->name_lc]) }}',
data: {noheader: true,value: item,loop: c++}, // @todo can we omit loop and c
cache: false
});
// @todo add any required attributes that are defined required as a result of adding this OC
// @todo Add attributes to "Add new Attribute" that are now available
});
// Loop through added_oc, and remove anything not in newadded
added_oc.forEach(function(item) {
if (newadded.indexOf(item) === -1) {
$('input[value="'+item+'"]').parent().empty();
// @todo remove any required attributes that are no longer defined as a result of removing this OC
// @todo Remove attributes from "Add new Attribute" that are no longer available
}
});
added_oc = newadded;
});
});
</script>
@append
@break
@case('App\Classes\LDAP\Attribute')
@default
@php($clone=TRUE)
<span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name_lc }}"><i class="fas fa-fw fa-plus"></i> @lang('Add Value')</span>
@section('page-scripts')
@if($clone && $edit && $o->can_addvalues)
<script type="text/javascript">
$(document).ready(function() {
// Create a new entry when Add Value clicked
$('#{{ $o->name_lc }}.addable').click(function (item) {
var cln = $(this).parent().parent().find('input:last').parent().clone();
cln.find('input:last').attr('value','').attr('placeholder', '[@lang('NEW')]');
cln.appendTo('#' + item.currentTarget.id)
});
});
</script>
@endif
@append
@endswitch
</span>
@endif

View File

@ -3,7 +3,7 @@
<input type="hidden" id="{{ $id ?? $name }}_disabled" name="{{ $name }}" value="" disabled> <input type="hidden" id="{{ $id ?? $name }}_disabled" name="{{ $name }}" value="" disabled>
@endisset @endisset
<select style="width: 80%" class="form-select @isset($name)@error((! empty($old)) ? $old : $name) is-invalid @enderror @endisset" id="{{ $id ?? $name }}" @isset($name)name="{{ $name }}"@endisset @required(isset($required) && $required) @disabled(isset($disabled) && $disabled)> <select style="width: 80%" class="form-select @isset($name)@error((! empty($old)) ? $old : $name) is-invalid @enderror @endisset" id="{{ $id ?? $name }}" @isset($name)name="{{ $name }}"@endisset @required(isset($required) && $required) @disabled(isset($disabled) && $disabled)>
@if(empty($value) || isset($addnew) || isset($choose)) @if((empty($value) && ! empty($options)) || isset($addnew) || isset($choose))
<option value=""></option> <option value=""></option>
@isset($addnew) @isset($addnew)
<option value="new">{{ $addnew ?: 'Add New' }}</option> <option value="new">{{ $addnew ?: 'Add New' }}</option>

View File

@ -126,8 +126,8 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Close</button> <button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary btn-sm" id="entry_export-download">Download</button> <button type="button" class="btn btn-sm btn-primary" id="entry_export-download">Download</button>
</div> </div>
</div> </div>
</div> </div>
@ -161,8 +161,8 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Close</button> <button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary btn-sm" id="userpassword_check-submit"><i class="fas fa-fw fa-spinner fa-spin d-none"></i> Check</button> <button type="button" class="btn btn-sm btn-primary" id="userpassword_check-submit"><i class="fas fa-fw fa-spinner fa-spin d-none"></i> Check</button>
</div> </div>
</div> </div>
</div> </div>
@ -193,6 +193,10 @@
// Find all input items and turn off readonly // Find all input items and turn off readonly
$('input.form-control').each(function() { $('input.form-control').each(function() {
// Except for objectClass - @todo show an "X" instead
if ($(this)[0].name.match(/^objectclass/))
return;
$(this).attr('readonly',false); $(this).attr('readonly',false);
}); });
@ -222,7 +226,7 @@
if (e.status != 412) if (e.status != 412)
alert('That didnt work? Please try again....'); alert('That didnt work? Please try again....');
}, },
url: '{{ url('entry/newattr') }}/'+item.target.value, url: '{{ url('entry/attr/add') }}/'+item.target.value,
cache: false cache: false
}); });

View File

@ -38,10 +38,11 @@ Route::group(['prefix'=>'user'],function() {
Route::get('image',[HomeController::class,'user_image']); Route::get('image',[HomeController::class,'user_image']);
}); });
Route::post('entry/update/commit',[HomeController::class,'entry_update']);
Route::post('entry/update/pending',[HomeController::class,'entry_pending_update']);
Route::get('entry/newattr/{id}',[HomeController::class,'entry_newattr']);
Route::get('entry/export/{id}',[HomeController::class,'entry_export']); Route::get('entry/export/{id}',[HomeController::class,'entry_export']);
Route::post('entry/password/check/',[HomeController::class,'entry_password_check']); Route::post('entry/password/check/',[HomeController::class,'entry_password_check']);
Route::get('entry/attr/add/{id}',[HomeController::class,'entry_attr_add']);
Route::post('entry/objectclass/add/{id}',[HomeController::class,'entry_objectclass_add']);
Route::post('entry/update/commit',[HomeController::class,'entry_update']);
Route::post('entry/update/pending',[HomeController::class,'entry_pending_update']);
Route::post('import/process/{type}',[HomeController::class,'import']); Route::post('import/process/{type}',[HomeController::class,'import']);