From 5ef021c3812794a9d9a7461681e531ab350e2bc0 Mon Sep 17 00:00:00 2001 From: Deon George <deon@dege.au> Date: Sun, 23 Mar 2025 22:16:26 +1100 Subject: [PATCH] Display a DN entry with language tags - work for #16 --- app/Classes/LDAP/Attribute.php | 47 +++++++++++--- app/Ldap/Entry.php | 4 +- app/View/Components/Attribute.php | 6 +- app/View/Components/AttributeType.php | 7 +- public/css/custom.css | 3 + .../views/components/attribute-type.blade.php | 2 +- .../views/components/attribute.blade.php | 37 ++++------- .../attribute/objectclass.blade.php | 4 +- .../components/attribute/password.blade.php | 2 +- .../attribute/widget/options.blade.php | 2 +- resources/views/fragment/dn/header.blade.php | 4 +- resources/views/frames/dn.blade.php | 64 +++++++++++++++++-- 12 files changed, 132 insertions(+), 50 deletions(-) diff --git a/app/Classes/LDAP/Attribute.php b/app/Classes/LDAP/Attribute.php index af398bc1..83abe2e8 100644 --- a/app/Classes/LDAP/Attribute.php +++ b/app/Classes/LDAP/Attribute.php @@ -178,14 +178,7 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator return $this->name; } - public function addValue(string $tag,string $value): void - { - $this->_values->put( - $tag, - $this->_values - ->get($tag,collect()) - ->push($value)); - } + /* INTERFACE */ public function current(): mixed { @@ -237,6 +230,24 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator // We cannot clear values using array syntax } + /* METHODS */ + + public function addValue(string $tag,array $values): void + { + $this->_values->put( + $tag, + array_unique(array_merge($this->_values + ->get($tag,[]),$values))); + } + + public function addValueOld(string $tag,array $values): void + { + $this->_values_old->put( + $tag, + array_unique(array_merge($this->_values_old + ->get($tag,[]),$values))); + } + /** * Return the hints about this attribute, ie: RDN, Required, etc * @@ -266,8 +277,10 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator */ public function isDirty(): bool { - return ($this->values_old->count() !== $this->values->count()) - || ($this->values->diff($this->values_old)->count() !== 0); + return (($a=$this->values_old->dot())->keys()->count() !== ($b=$this->values->dot())->keys()->count()) + || ($a->values()->count() !== $b->values()->count()) + || ($a->keys()->diff($b->keys())->count() !== 0) + || ($a->values()->diff($b->values())->count() !== 0); } /** @@ -329,4 +342,18 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator ? $this->oc->intersect($this->required_by->keys())->sort() : collect(); } + + public function tagValues(string $tag=''): Collection + { + return $this->_values + ->filter(fn($item,$key)=>($key === $tag)) + ->values(); + } + + public function tagValuesOld(string $tag=''): Collection + { + return $this->_values_old + ->filter(fn($item,$key)=>($key === $tag)) + ->values(); + } } \ No newline at end of file diff --git a/app/Ldap/Entry.php b/app/Ldap/Entry.php index 6754f38a..73ce9d37 100644 --- a/app/Ldap/Entry.php +++ b/app/Ldap/Entry.php @@ -309,7 +309,9 @@ class Entry extends Model ->map(fn($item)=>$item ->values ->keys() - ->filter(fn($item)=>preg_match(sprintf('/%s+;?/',self::TAG_CHARS_LANG),$item))) + ->filter(fn($item)=>preg_match(sprintf('/%s+;?/',self::TAG_CHARS_LANG),$item)) + ->map(fn($item)=>preg_replace('/lang-/','',$item)) + ) ->filter(fn($item)=>$item->count()); } diff --git a/app/View/Components/Attribute.php b/app/View/Components/Attribute.php index fd866f93..0e78ee4c 100644 --- a/app/View/Components/Attribute.php +++ b/app/View/Components/Attribute.php @@ -12,17 +12,19 @@ class Attribute extends Component public bool $edit; public bool $new; public bool $old; - public ?string $na; + public string $langtag; + public ?string $na; // Text to render if the LDAPAttribute is null /** * Create a new component instance. */ - public function __construct(?LDAPAttribute $o,bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,?string $na=NULL) + public function __construct(?LDAPAttribute $o,bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag='',?string $na=NULL) { $this->o = $o; $this->edit = $edit; $this->old = $old; $this->new = $new; + $this->langtag = $langtag; $this->na = $na; } diff --git a/app/View/Components/AttributeType.php b/app/View/Components/AttributeType.php index 6c3dcb57..b7af118c 100644 --- a/app/View/Components/AttributeType.php +++ b/app/View/Components/AttributeType.php @@ -13,14 +13,16 @@ class AttributeType extends Component { private LDAPAttribute $o; private bool $new; + private string $langtag; /** * Create a new component instance. */ - public function __construct(LDAPAttribute $o,bool $new=FALSE) + public function __construct(LDAPAttribute $o,bool $new=FALSE,string $langtag='') { $this->o = $o; $this->new = $new; + $this->langtag = $langtag; } /** @@ -30,6 +32,7 @@ class AttributeType extends Component { return view('components.attribute-type') ->with('o',$this->o) - ->with('new',$this->new); + ->with('new',$this->new) + ->with('langtag',$this->langtag); } } \ No newline at end of file diff --git a/public/css/custom.css b/public/css/custom.css index a0b522fe..d61392fa 100644 --- a/public/css/custom.css +++ b/public/css/custom.css @@ -30,7 +30,10 @@ input.form-control.input-group-end { .custom-tooltip-danger { --bs-tooltip-bg: var(--bs-danger); +} +.custom-tooltip { + --bs-tooltip-bg: var(--bs-gray-900); } .tooltip { diff --git a/resources/views/components/attribute-type.blade.php b/resources/views/components/attribute-type.blade.php index 9fa95a38..feb96553 100644 --- a/resources/views/components/attribute-type.blade.php +++ b/resources/views/components/attribute-type.blade.php @@ -15,7 +15,7 @@ </div> </div> - <x-attribute :o="$o" :edit="true" :new="$new ?? FALSE"/> + <x-attribute :o="$o" :edit="true" :new="$new ?? FALSE" :langtag="$langtag"/> </div> </div> diff --git a/resources/views/components/attribute.blade.php b/resources/views/components/attribute.blade.php index db84c49c..ace574a3 100644 --- a/resources/views/components/attribute.blade.php +++ b/resources/views/components/attribute.blade.php @@ -1,31 +1,22 @@ <!-- $o=Attribute::class --> <x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o"> - @foreach(old($o->name_lc,($new ?? FALSE) ? [NULL] : $o->values) as $tag=>$tagvalues) - <div class="row p-2 border rounded"> - <div class="col-2"> - {{ $tag }} - </div> - <div class="col-10"> - <div class="row"> - <div class="col-12"> - @foreach($tagvalues as $value) - @if(($edit ?? FALSE) && ! $o->is_rdn) - <div class="input-group has-validation"> - <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(! ($new ?? FALSE))> + @foreach(old($o->name_lc,($new ?? FALSE) ? [NULL] : $o->tagValues($langtag)) as $values) + <div class="col-12"> + @foreach($values as $value) + @if(($edit ?? FALSE) && ! $o->is_rdn) + <div class="input-group has-validation"> + <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(! ($new ?? FALSE))> - <div class="invalid-feedback pb-2"> - @if($e) - {{ join('|',$e) }} - @endif - </div> - </div> - @else - {{ $value }} + <div class="invalid-feedback pb-2"> + @if($e) + {{ join('|',$e) }} @endif - @endforeach + </div> </div> - </div> - </div> + @else + {{ $value }} + @endif + @endforeach </div> @endforeach </x-attribute.layout> \ No newline at end of file diff --git a/resources/views/components/attribute/objectclass.blade.php b/resources/views/components/attribute/objectclass.blade.php index 22b9f7f4..02522d9d 100644 --- a/resources/views/components/attribute/objectclass.blade.php +++ b/resources/views/components/attribute/objectclass.blade.php @@ -1,8 +1,8 @@ <!-- $o=Attribute::class --> -<x-attribute.layout :edit="$edit" :new="$new" :o="$o"> +<x-attribute.layout :edit="$edit" :new="$new" :o="$o" :langtag="$langtag"> @foreach(old($o->name_lc,$o->values) as $value) @if($edit) - <x-attribute.widget.objectclass :o="$o" :edit="$edit" :new="$new" :loop="$loop" :value="$value"/> + <x-attribute.widget.objectclass :o="$o" :edit="$edit" :new="$new" :loop="$loop" :value="$value" :langtag="$langtag"/> @else {{ $value }} @if ($o->isStructural($value)) diff --git a/resources/views/components/attribute/password.blade.php b/resources/views/components/attribute/password.blade.php index 943f4b6b..8448ebea 100644 --- a/resources/views/components/attribute/password.blade.php +++ b/resources/views/components/attribute/password.blade.php @@ -1,6 +1,6 @@ <!-- @todo We are not handling redirect backs yet with updated passwords --> <!-- $o=Password::class --> -<x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o"> +<x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o" :langtag="$langtag"> @foreach($o->values_old as $value) @if($edit) <div class="input-group has-validation mb-3"> diff --git a/resources/views/components/attribute/widget/options.blade.php b/resources/views/components/attribute/widget/options.blade.php index eaf55b48..37097185 100644 --- a/resources/views/components/attribute/widget/options.blade.php +++ b/resources/views/components/attribute/widget/options.blade.php @@ -4,7 +4,7 @@ <span class="p-0 m-0"> @if($o->is_rdn) <br/> - <span class="btn btn-sm btn-outline-focus mt-3" disabled><i class="fas fa-fw fa-exchange"></i> @lang('Rename')</span> + <button class="btn btn-sm btn-outline-focus mt-3" disabled><i class="fas fa-fw fa-exchange"></i> @lang('Rename')</button> @elseif($edit && $o->can_addvalues) @switch(get_class($o)) @case(JpegPhoto::class) diff --git a/resources/views/fragment/dn/header.blade.php b/resources/views/fragment/dn/header.blade.php index fddc54d1..0160c9f3 100644 --- a/resources/views/fragment/dn/header.blade.php +++ b/resources/views/fragment/dn/header.blade.php @@ -26,10 +26,10 @@ <x-attribute :o="$o->getObject('entryuuid')" :na="__('Unknown')"/> </th> </tr> - @if(($x=$o->getLangTags())->count()) + @if($langtags->count()) <tr class="mt-1"> <td class="p-0 pe-2">Tags</td> - <th class="p-0">{{ $x->flatMap(fn($item)=>$item->values())->unique()->join(', ') }}</th> + <th class="p-0">{{ $langtags->join(', ') }}</th> </tr> @endif </table> diff --git a/resources/views/frames/dn.blade.php b/resources/views/frames/dn.blade.php index 931352ef..60081b80 100644 --- a/resources/views/frames/dn.blade.php +++ b/resources/views/frames/dn.blade.php @@ -1,7 +1,13 @@ @extends('layouts.dn') @section('page_title') - @include('fragment.dn.header',['o'=>($o ?? $o=$server->fetch($dn))]) + @include('fragment.dn.header',[ + 'o'=>($o ?? $o=$server->fetch($dn)), + 'langtags'=>($langtags=$o->getLangTags() + ->flatMap(fn($item)=>$item->values()) + ->unique() + ->sort()) + ]) @endsection @section('page_actions') @@ -71,7 +77,7 @@ <div class="main-card mb-3 card"> <div class="card-body"> <div class="card-header-tabs"> - <ul class="nav nav-tabs"> + <ul class="nav nav-tabs mb-0"> <li class="nav-item"><a data-bs-toggle="tab" href="#attributes" class="nav-link active">@lang('Attributes')</a></li> <li class="nav-item"><a data-bs-toggle="tab" href="#internal" class="nav-link">@lang('Internal')</a></li> @env(['local']) @@ -87,9 +93,57 @@ <input type="hidden" name="dn" value=""> - @foreach ($o->getVisibleAttributes() as $ao) - <x-attribute-type :edit="true" :o="$ao"/> - @endforeach + <div class="card-header border-bottom-0"> + <div class="btn-actions-pane-right"> + <div role="group" class="btn-group-sm nav btn-group"> + @foreach($langtags->prepend('')->push('+') as $tag) + <a data-bs-toggle="tab" href="#tab-lang-{{ $tag ?: '_default' }}" class="btn btn-outline-light border-dark-subtle @if(! $loop->index) active @endif @if($loop->last)ndisabled @endif"> + @switch($tag) + @case('') + <i class="fas fa-fw fa-border-none" data-bs-toggle="tooltip" data-bs-custom-class="custom-tooltip" title="@lang('No Lang Tag')"></i> + @break + + @case('+') + <!-- @todo To implement --> + <i class="fas fa-fw fa-plus text-dark" data-bs-toggle="tooltip" data-bs-custom-class="custom-tooltip" title="@lang('Add Lang Tag')"></i> + @break + + @default + <span class="f16" data-bs-toggle="tooltip" data-bs-custom-class="custom-tooltip" title="{{ strtoupper($tag) }}"><i class="flag {{ $tag }}"></i></span> + @endswitch + </a> + @endforeach + </div> + </div> + </div> + + <div class="card-body"> + <div class="tab-content"> + @foreach($langtags as $tag) + <div class="tab-pane @if(! $loop->index) active @endif" id="tab-lang-{{ $tag ?: '_default' }}" role="tabpanel"> + @switch($tag) + @case('') + @foreach ($o->getVisibleAttributes($tag) as $ao) + <x-attribute-type :edit="true" :o="$ao" langtag=""/> + @endforeach + @break + + @case('+') + <div class="ms-auto mt-4 alert alert-warning p-2" style="max-width: 30em; font-size: 0.80em;"> + It is not possible to create new language tags at the moment. This functionality should come soon.<br> + You can create them with an LDIF import though. + </div> + @break + + @default + @foreach ($o->getVisibleAttributes($langtag=sprintf('lang-%s',$tag)) as $ao) + <x-attribute-type :edit="true" :o="$ao" :langtag="$langtag"/> + @endforeach + @endswitch + </div> + @endforeach + </div> + </div> @include('fragment.dn.add_attr') </form>