Render HTML inputs for a DN with language tags - work for #16

This commit is contained in:
Deon George 2025-04-05 11:38:07 +11:00
parent 633513d3e9
commit cf535286c5
25 changed files with 244 additions and 200 deletions

View File

@ -7,6 +7,7 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use App\Classes\LDAP\Schema\AttributeType; use App\Classes\LDAP\Schema\AttributeType;
use App\Ldap\Entry;
/** /**
* Represents an attribute of an LDAP Object * Represents an attribute of an LDAP Object
@ -100,9 +101,9 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
{ {
$this->dn = $dn; $this->dn = $dn;
$this->name = $name; $this->name = $name;
$this->values_old = collect($values); $this->_values = collect($values);
$this->_values_old = collect($values);
$this->values = collect();
$this->oc = collect($oc); $this->oc = collect($oc);
$this->schema = (new Server) $this->schema = (new Server)
@ -149,15 +150,15 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
// Used in Object Classes // Used in Object Classes
'used_in' => $this->schema?->used_in_object_classes ?: collect(), 'used_in' => $this->schema?->used_in_object_classes ?: collect(),
// The current attribute values // The current attribute values
'values' => $this->no_attr_tags ? collect($this->_values->first()) : $this->_values, 'values' => $this->no_attr_tags ? $this->tagValues() : $this->_values,
// The original attribute values // The original attribute values
'values_old' => $this->no_attr_tags ? collect($this->_values_old->first()) : $this->_values_old, 'values_old' => $this->no_attr_tags ? $this->tagValuesOld() : $this->_values_old,
default => throw new \Exception('Unknown key:' . $key), default => throw new \Exception('Unknown key:' . $key),
}; };
} }
public function __set(string $key,mixed $values) public function __set(string $key,mixed $values): void
{ {
switch ($key) { switch ($key) {
case 'values': case 'values':
@ -320,14 +321,14 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
->with('new',$new); ->with('new',$new);
} }
public function render_item_old(int $key): ?string public function render_item_old(string $dotkey): ?string
{ {
return Arr::get($this->values_old,$key); return Arr::get($this->_values_old->dot(),$dotkey);
} }
public function render_item_new(int $key): ?string public function render_item_new(string $dotkey): ?string
{ {
return Arr::get($this->values,$key); return Arr::get($this->_values->dot(),$dotkey);
} }
/** /**
@ -343,17 +344,17 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
: collect(); : collect();
} }
public function tagValues(string $tag=''): Collection public function tagValues(string $tag=Entry::TAG_NOTAG): Collection
{ {
return $this->_values return collect($this->_values
->filter(fn($item,$key)=>($key === $tag)) ->filter(fn($item,$key)=>($key===$tag))
->values(); ->get($tag,[]));
} }
public function tagValuesOld(string $tag=''): Collection public function tagValuesOld(string $tag=Entry::TAG_NOTAG): Collection
{ {
return $this->_values_old return collect($this->_values_old
->filter(fn($item,$key)=>($key === $tag)) ->filter(fn($item,$key)=>($key===$tag))
->values(); ->get($tag,[]));
} }
} }

View File

@ -5,6 +5,7 @@ namespace App\Classes\LDAP\Attribute\Binary;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute\Binary; use App\Classes\LDAP\Attribute\Binary;
use App\Ldap\Entry;
use App\Traits\MD5Updates; use App\Traits\MD5Updates;
/** /**
@ -14,13 +15,14 @@ final class JpegPhoto extends Binary
{ {
use MD5Updates; use MD5Updates;
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG): View
{ {
return view('components.attribute.binary.jpegphoto') return view('components.attribute.binary.jpegphoto')
->with('o',$this) ->with('o',$this)
->with('edit',$edit) ->with('edit',$edit)
->with('old',$old) ->with('old',$old)
->with('new',$new) ->with('new',$new)
->with('langtag',$langtag)
->with('f',new \finfo); ->with('f',new \finfo);
} }
} }

View File

@ -26,18 +26,16 @@ final class KrbPrincipalKey extends Attribute
->with('new',$new); ->with('new',$new);
} }
public function render_item_old(int $key): ?string public function render_item_old(string $dotkey): ?string
{ {
$pw = Arr::get($this->values_old,$key); return parent::render_item_old($dotkey)
return $pw
? str_repeat('*',16) ? str_repeat('*',16)
: NULL; : NULL;
} }
public function render_item_new(int $key): ?string public function render_item_new(string $dotkey): ?string
{ {
$pw = Arr::get($this->values,$key); return parent::render_item_new($dotkey)
return $pw
? str_repeat('*',16) ? str_repeat('*',16)
: NULL; : NULL;
} }

View File

@ -3,9 +3,9 @@
namespace App\Classes\LDAP\Attribute; namespace App\Classes\LDAP\Attribute;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use App\Classes\LDAP\Attribute; use App\Classes\LDAP\Attribute;
use Illuminate\Support\Collection;
/** /**
* Represents an attribute whose value is a Kerberos Ticket Flag * Represents an attribute whose value is a Kerberos Ticket Flag

View File

@ -29,19 +29,33 @@ final class ObjectClass extends Attribute
{ {
parent::__construct($dn,$name,$values,['top']); parent::__construct($dn,$name,$values,['top']);
$this->oc_schema = config('server') $this->set_oc_schema($this->tagValuesOld());
->schema('objectclasses')
->filter(fn($item)=>$this->values_old->contains($item->name));
} }
public function __get(string $key): mixed public function __get(string $key): mixed
{ {
return match ($key) { return match ($key) {
'structural' => $this->oc_schema->filter(fn($item) => $item->isStructural()), 'structural' => $this->oc_schema->filter(fn($item)=>$item->isStructural()),
default => parent::__get($key), default => parent::__get($key),
}; };
} }
public function __set(string $key,mixed $values): void
{
switch ($key) {
case 'values':
parent::__set($key,$values);
// We need to populate oc_schema, if we are a new OC and thus dont have any old values
if (! $this->values_old->count() && $this->values->count())
$this->set_oc_schema($this->tagValues());
break;
default: parent::__set($key,$values);
}
}
/** /**
* Is a specific value the structural objectclass * Is a specific value the structural objectclass
* *
@ -63,4 +77,11 @@ final class ObjectClass extends Attribute
->with('old',$old) ->with('old',$old)
->with('new',$new); ->with('new',$new);
} }
private function set_oc_schema(Collection $tv): void
{
$this->oc_schema = config('server')
->schema('objectclasses')
->filter(fn($item)=>$tv->contains($item->name));
}
} }

View File

@ -88,19 +88,23 @@ final class Password extends Attribute
->with('helpers',static::helpers()->map(fn($item,$key)=>['id'=>$key,'value'=>$key])->sort()); ->with('helpers',static::helpers()->map(fn($item,$key)=>['id'=>$key,'value'=>$key])->sort());
} }
public function render_item_old(int $key): ?string public function render_item_old(string $dotkey): ?string
{ {
$pw = Arr::get($this->values_old,$key); $pw = parent::render_item_old($dotkey);
return $pw return $pw
? (((($x=$this->hash($pw)) && ($x::id() !== '*clear*')) ? sprintf('{%s}',$x::shortid()) : '').str_repeat('*',16)) ? (((($x=$this->hash($pw)) && ($x::id() === '*clear*')) ? sprintf('{%s}',$x::shortid()) : '')
.str_repeat('*',16))
: NULL; : NULL;
} }
public function render_item_new(int $key): ?string public function render_item_new(string $dotkey): ?string
{ {
$pw = Arr::get($this->values,$key); $pw = parent::render_item_new($dotkey);
return $pw return $pw
? (((($x=$this->hash($pw)) && ($x::id() !== '*clear*')) ? sprintf('{%s}',$x::shortid()) : '').str_repeat('*',16)) ? (((($x=$this->hash($pw)) && ($x::id() !== '*clear*')) ? sprintf('{%s}',$x::shortid()) : '')
.str_repeat('*',16))
: NULL; : NULL;
} }
} }

View File

@ -5,6 +5,7 @@ namespace App\Classes\LDAP\Export;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use App\Classes\LDAP\Export; use App\Classes\LDAP\Export;
use App\Ldap\Entry;
/** /**
* Export from LDAP using an LDIF format * Export from LDAP using an LDIF format
@ -55,8 +56,8 @@ class LDIF extends Export
foreach ($tagvalues as $value) { foreach ($tagvalues as $value) {
$result .= $this->multiLineDisplay( $result .= $this->multiLineDisplay(
Str::isAscii($value) Str::isAscii($value)
? sprintf('%s: %s',$ao->name.($tag ? ';'.$tag : ''),$value) ? sprintf('%s: %s',$ao->name.(($tag !== Entry::TAG_NOTAG) ? ';'.$tag : ''),$value)
: sprintf('%s:: %s',$ao->name.($tag ? ';'.$tag : ''),base64_encode($value)) : sprintf('%s:: %s',$ao->name.(($tag !== Entry::TAG_NOTAG) ? ';'.$tag : ''),base64_encode($value))
,$this->br); ,$this->br);
} }
} }

View File

@ -58,10 +58,10 @@ class HomeController extends Controller
$o = new Entry; $o = new Entry;
if (count(array_filter($x=old('objectclass',$request->objectclass)))) { if (count(array_filter($x=old('objectclass',$request->objectclass)))) {
$o->objectclass = $x; $o->objectclass = [Entry::TAG_NOTAG=>$x];
foreach($o->getAvailableAttributes()->filter(fn($item)=>$item->required) as $ao) foreach($o->getAvailableAttributes()->filter(fn($item)=>$item->required) as $ao)
$o->{$ao->name} = ''; $o->{$ao->name} = [Entry::TAG_NOTAG=>''];
$o->setRDNBase($key['dn']); $o->setRDNBase($key['dn']);
} }
@ -188,8 +188,6 @@ class HomeController extends Controller
$result = (new Entry) $result = (new Entry)
->query() ->query()
//->cache(Carbon::now()->addSeconds(Config::get('ldap.cache.time')))
//->select(['*'])
->setDn($dn) ->setDn($dn)
->recursive() ->recursive()
->get(); ->get();

View File

@ -23,6 +23,7 @@ class Entry extends Model
{ {
private const TAG_CHARS = 'a-zA-Z0-9-'; private const TAG_CHARS = 'a-zA-Z0-9-';
private const TAG_CHARS_LANG = 'lang-['.self::TAG_CHARS.']'; private const TAG_CHARS_LANG = 'lang-['.self::TAG_CHARS.']';
public const TAG_NOTAG = '_null_';
// Our Attribute objects // Our Attribute objects
private Collection $objects; private Collection $objects;
@ -53,8 +54,9 @@ class Entry extends Model
/** /**
* This function overrides getAttributes to use our collection of Attribute objects instead of the models attributes. * This function overrides getAttributes to use our collection of Attribute objects instead of the models attributes.
* *
* This returns an array that should be consistent with $this->attributes
*
* @return array * @return array
* @note $this->attributes may not be updated with changes
*/ */
public function getAttributes(): array public function getAttributes(): array
{ {
@ -63,7 +65,7 @@ class Entry extends Model
($item->no_attr_tags) ($item->no_attr_tags)
? [strtolower($item->name)=>$item->values] ? [strtolower($item->name)=>$item->values]
: $item->values : $item->values
->flatMap(fn($v,$k)=>[strtolower($item->name.($k ? ';'.$k : ''))=>$v])) ->flatMap(fn($v,$k)=>[strtolower($item->name.($k !== self::TAG_NOTAG ? ';'.$k : ''))=>$v]))
->toArray(); ->toArray();
} }
@ -76,12 +78,10 @@ class Entry extends Model
{ {
$key = $this->normalizeAttributeKey($key); $key = $this->normalizeAttributeKey($key);
// @todo Silently ignore keys of language tags - we should work with them list($attribute,$tag) = $this->keytag($key);
if (str_contains($key,';'))
return TRUE;
return ((! array_key_exists($key,$this->original)) && (! $this->objects->has($key))) return ((! array_key_exists($key,$this->original)) && (! $this->objects->has($attribute)))
|| (! $this->getObject($key)->isDirty()); || (! $this->getObject($attribute)->isDirty());
} }
public static function query(bool $noattrs=false): Builder public static function query(bool $noattrs=false): Builder
@ -98,18 +98,22 @@ class Entry extends Model
* As attribute values are updated, or new ones created, we need to mirror that * As attribute values are updated, or new ones created, we need to mirror that
* into our $objects. This is called when we $o->key = $value * into our $objects. This is called when we $o->key = $value
* *
* This function should update $this->attributes and correctly reflect changes in $this->objects
*
* @param string $key * @param string $key
* @param mixed $value * @param mixed $value
* @return $this * @return $this
*/ */
public function setAttribute(string $key,mixed $value): static public function setAttribute(string $key,mixed $value): static
{ {
parent::setAttribute($key,$value); foreach ($value as $k => $v)
parent::setAttribute($key.($k !== self::TAG_NOTAG ? ';'.$k : ''),$v);
$key = $this->normalizeAttributeKey($key); $key = $this->normalizeAttributeKey($key);
list($attribute,$tags) = $this->keytag($key);
$o = $this->objects->get($key) ?: Factory::create($this->dn ?: '',$key,[],Arr::get($this->attributes,'objectclass',[])); $o = $this->objects->get($attribute) ?: Factory::create($this->dn ?: '',$attribute,[],Arr::get($this->attributes,'objectclass',[]));
$o->values = collect($this->attributes[$key]); $o->values = collect($value);
$this->objects->put($key,$o); $this->objects->put($key,$o);
@ -173,21 +177,13 @@ class Entry extends Model
$key = $this->normalizeAttributeKey(strtolower($key)); $key = $this->normalizeAttributeKey(strtolower($key));
// If the attribute name has tags // If the attribute name has tags
$matches = []; list($attribute,$tag) = $this->keytag($key);
if (preg_match(sprintf('/^([%s]+);+([%s;]+)/',self::TAG_CHARS,self::TAG_CHARS),$key,$matches)) {
$attribute = $matches[1];
$tags = $matches[2];
} else {
$attribute = $key;
$tags = '';
}
if (! config('server')->schema('attributetypes')->has($attribute)) if (! config('server')->schema('attributetypes')->has($attribute))
throw new AttributeException(sprintf('Schema doesnt have attribute [%s]',$attribute)); throw new AttributeException(sprintf('Schema doesnt have attribute [%s]',$attribute));
$o = $this->objects->get($attribute) ?: Attribute\Factory::create($this->dn ?: '',$attribute,[]); $o = $this->objects->get($attribute) ?: Attribute\Factory::create($this->dn ?: '',$attribute,[]);
$o->addValue($tags,$value); $o->addValue($tag,[$value]);
$this->objects->put($attribute,$o); $this->objects->put($attribute,$o);
} }
@ -203,16 +199,7 @@ class Entry extends Model
$entry_oc = Arr::get($this->attributes,'objectclass',[]); $entry_oc = Arr::get($this->attributes,'objectclass',[]);
foreach ($this->attributes as $attrtag => $values) { foreach ($this->attributes as $attrtag => $values) {
// If the attribute name has tags list($attribute,$tags) = $this->keytag($attrtag);
$matches = [];
if (preg_match(sprintf('/^([%s]+);+([%s;]+)/',self::TAG_CHARS,self::TAG_CHARS),$attrtag,$matches)) {
$attribute = $matches[1];
$tags = $matches[2];
} else {
$attribute = $attrtag;
$tags = NULL;
}
$orig = Arr::get($this->original,$attrtag,[]); $orig = Arr::get($this->original,$attrtag,[]);
@ -227,7 +214,8 @@ class Entry extends Model
$entry_oc, $entry_oc,
)); ));
$o->values = $o->values->merge([$tags=>$values]); $o->addValue($tags,$values);
$o->addValueOld($tags,Arr::get($this->original,$attrtag));
$result->put($attribute,$o); $result->put($attribute,$o);
} }
@ -270,7 +258,7 @@ class Entry extends Model
{ {
$result = collect(); $result = collect();
foreach ($this->objectclass as $oc) foreach ($this->getObject('objectclass')->values as $oc)
$result = $result->merge(config('server')->schema('objectclasses',$oc)->attributes); $result = $result->merge(config('server')->schema('objectclasses',$oc)->attributes);
return $result; return $result;
@ -369,7 +357,8 @@ class Entry extends Model
->filter(fn($item)=> ->filter(fn($item)=>
$item && collect(explode(';',$item))->filter( $item && collect(explode(';',$item))->filter(
fn($item)=> fn($item)=>
(! preg_match(sprintf('/^%s+$/',self::TAG_CHARS_LANG),$item)) (! preg_match(sprintf('/^%s$/',self::TAG_NOTAG),$item))
&& (! preg_match(sprintf('/^%s+$/',self::TAG_CHARS_LANG),$item))
&& (! preg_match('/^binary$/',$item)) && (! preg_match('/^binary$/',$item))
) )
->count()) ->count())
@ -381,6 +370,9 @@ class Entry extends Model
* Return a list of attributes without any values * Return a list of attributes without any values
* *
* @return Collection * @return Collection
* @todo Dont show attributes that are not provided by an objectclass, make a new function to show those
* This is for dynamic list items eg: labeledURI, which are not editable.
* We can highlight those values that are as a result of a dynamic module
*/ */
public function getMissingAttributes(): Collection public function getMissingAttributes(): Collection
{ {
@ -401,12 +393,14 @@ class Entry extends Model
/** /**
* Return this list of user attributes * Return this list of user attributes
* *
* @param string|null $tag If null return all tags
* @return Collection * @return Collection
*/ */
public function getVisibleAttributes(): Collection public function getVisibleAttributes(?string $tag=NULL): Collection
{ {
return $this->objects return $this->objects
->filter(fn($item)=>! $item->is_internal); ->filter(fn($item)=>! $item->is_internal)
->filter(fn($item)=>is_null($tag) || count($item->tagValues($tag)) > 0);
} }
public function hasAttribute(int|string $key): bool public function hasAttribute(int|string $key): bool
@ -452,65 +446,92 @@ class Entry extends Model
*/ */
public function icon(): string public function icon(): string
{ {
$objectclasses = array_map('strtolower',$this->objectclass); $objectclasses = $this->getObject('objectclass')
->tagValues()
->map(fn($item)=>strtolower($item));
// Return icon based upon objectClass value // Return icon based upon objectClass value
if (in_array('person',$objectclasses) || if ($objectclasses->intersect([
in_array('organizationalperson',$objectclasses) || 'account',
in_array('inetorgperson',$objectclasses) || 'inetorgperson',
in_array('account',$objectclasses) || 'organizationalperson',
in_array('posixaccount',$objectclasses)) 'person',
'posixaccount',
])->count())
return 'fas fa-user'; return 'fas fa-user';
elseif (in_array('organization',$objectclasses)) elseif ($objectclasses->contains('organization'))
return 'fas fa-university'; return 'fas fa-university';
elseif (in_array('organizationalunit',$objectclasses)) elseif ($objectclasses->contains('organizationalunit'))
return 'fas fa-object-group'; return 'fas fa-object-group';
elseif (in_array('posixgroup',$objectclasses) || elseif ($objectclasses->intersect([
in_array('groupofnames',$objectclasses) || 'posixgroup',
in_array('groupofuniquenames',$objectclasses) || 'groupofnames',
in_array('group',$objectclasses)) 'groupofuniquenames',
'group',
])->count())
return 'fas fa-users'; return 'fas fa-users';
elseif (in_array('dcobject',$objectclasses) || elseif ($objectclasses->intersect([
in_array('domainrelatedobject',$objectclasses) || 'dcobject',
in_array('domain',$objectclasses) || 'domainrelatedobject',
in_array('builtindomain',$objectclasses)) 'domain',
'builtindomain',
])->count())
return 'fas fa-network-wired'; return 'fas fa-network-wired';
elseif (in_array('alias',$objectclasses)) elseif ($objectclasses->contains('alias'))
return 'fas fa-theater-masks'; return 'fas fa-theater-masks';
elseif (in_array('country',$objectclasses)) elseif ($objectclasses->contains('country'))
return sprintf('flag %s',strtolower(Arr::get($this->c ?: [],0))); return sprintf('flag %s',strtolower(Arr::get($this->c ?: [],0)));
elseif (in_array('device',$objectclasses)) elseif ($objectclasses->contains('device'))
return 'fas fa-mobile-alt'; return 'fas fa-mobile-alt';
elseif (in_array('document',$objectclasses)) elseif ($objectclasses->contains('document'))
return 'fas fa-file-alt'; return 'fas fa-file-alt';
elseif (in_array('iphost',$objectclasses)) elseif ($objectclasses->contains('iphost'))
return 'fas fa-wifi'; return 'fas fa-wifi';
elseif (in_array('room',$objectclasses)) elseif ($objectclasses->contains('room'))
return 'fas fa-door-open'; return 'fas fa-door-open';
elseif (in_array('server',$objectclasses)) elseif ($objectclasses->contains('server'))
return 'fas fa-server'; return 'fas fa-server';
elseif (in_array('openldaprootdse',$objectclasses)) elseif ($objectclasses->contains('openldaprootdse'))
return 'fas fa-info'; return 'fas fa-info';
// Default // Default
return 'fa-fw fas fa-cog'; return 'fa-fw fas fa-cog';
} }
/**
* Given an LDAP attribute, this will return the attribute name and the tag
* eg: description;lang-cn will return [description,lang-cn]
*
* @param string $key
* @return array
*/
private function keytag(string $key): array
{
$matches = [];
if (preg_match(sprintf('/^([%s]+);+([%s;]+)/',self::TAG_CHARS,self::TAG_CHARS),$key,$matches)) {
$attribute = $matches[1];
$tags = $matches[2];
} else {
$attribute = $key;
$tags = self::TAG_NOTAG;
}
return [$attribute,$tags];
}
/** /**
* Dont convert our $this->attributes to $this->objects when creating a new Entry::class * Dont convert our $this->attributes to $this->objects when creating a new Entry::class
* *

View File

@ -5,6 +5,7 @@ namespace App\View\Components;
use Illuminate\View\Component; use Illuminate\View\Component;
use App\Classes\LDAP\Attribute as LDAPAttribute; use App\Classes\LDAP\Attribute as LDAPAttribute;
use App\Ldap\Entry;
class Attribute extends Component class Attribute extends Component
{ {
@ -15,29 +16,29 @@ class Attribute extends Component
public string $langtag; public string $langtag;
public ?string $na; // Text to render if the LDAPAttribute is null public ?string $na; // Text to render if the LDAPAttribute is null
/** /**
* Create a new component instance. * Create a new component instance.
*/ */
public function __construct(?LDAPAttribute $o,bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag='',?string $na=NULL) public function __construct(?LDAPAttribute $o,bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,?string $na=NULL)
{ {
$this->o = $o; $this->o = $o;
$this->edit = $edit; $this->edit = $edit;
$this->old = $old; $this->old = $old;
$this->new = $new; $this->new = $new;
$this->langtag = $langtag; $this->langtag = $langtag;
$this->na = $na; $this->na = $na;
} }
/** /**
* Get the view / contents that represent the component. * Get the view / contents that represent the component.
* *
* @return \Illuminate\Contracts\View\View|\Closure|string * @return \Illuminate\Contracts\View\View|\Closure|string
*/ */
public function render() public function render()
{ {
return $this->o return $this->o
? $this->o ? $this->o
->render($this->edit,$this->old,$this->new) ->render($this->edit,$this->old,$this->new)
: $this->na; : $this->na;
} }
} }

View File

@ -4,10 +4,10 @@ namespace App\View\Components;
use Closure; use Closure;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use Illuminate\View\Component; use Illuminate\View\Component;
use App\Classes\LDAP\Attribute as LDAPAttribute; use App\Classes\LDAP\Attribute as LDAPAttribute;
use App\Ldap\Entry;
class AttributeType extends Component class AttributeType extends Component
{ {
@ -18,7 +18,7 @@ class AttributeType extends Component
/** /**
* Create a new component instance. * Create a new component instance.
*/ */
public function __construct(LDAPAttribute $o,bool $new=FALSE,string $langtag='') public function __construct(LDAPAttribute $o,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG)
{ {
$this->o = $o; $this->o = $o;
$this->new = $new; $this->new = $new;

View File

@ -1,22 +1,21 @@
<!-- $o=Attribute::class --> <!-- $o=Attribute::class -->
<x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o"> <x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o">
@foreach(old($o->name_lc,($new ?? FALSE) ? [NULL] : $o->tagValues($langtag)) as $values) <div class="col-12">
<div class="col-12"> @foreach(Arr::get(old($o->name_lc,[$langtag=>($new ?? FALSE) ? [NULL] : $o->tagValues($langtag)]),$langtag) as $key => $value)
@foreach($values as $value) @if(($edit ?? FALSE) && ! $o->is_rdn)
@if(($edit ?? FALSE) && ! $o->is_rdn) <div class="input-group has-validation">
<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'=>! ($tv=$o->tagValuesOld($langtag))->contains($value)]) name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ $value }}" placeholder="{{ ! is_null($x=$tv->get($loop->index)) ? $x : '['.__('NEW').']' }}" @readonly(! ($new ?? FALSE))>
<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"> <div class="invalid-feedback pb-2">
@if($e) @if($e)
{{ join('|',$e) }} {{ join('|',$e) }}
@endif @endif
</div>
</div> </div>
@else </div>
{{ $value }}
@endif @else
@endforeach <input type="text" class="form-control mb-1" value="{{ $value }}" disabled>
</div> @endif
@endforeach @endforeach
</div>
</x-attribute.layout> </x-attribute.layout>

View File

@ -1,17 +1,17 @@
<!-- @todo We are not handling redirect backs yet with updated photos --> <!-- @todo We are not handling redirect backs yet with updated photos -->
<!-- $o=Binary\JpegPhoto::class --> <!-- $o=Binary\JpegPhoto::class -->
<x-attribute.layout :edit="$edit" :new="false" :o="$o"> <x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o" :langtag="$langtag">
<table class="table table-borderless p-0 m-0"> <table class="table table-borderless p-0 m-0">
@foreach ($o->values_old as $value) @foreach($o->tagValuesOld() as $key => $value)
<tr> <tr>
@switch ($x=$f->buffer($value,FILEINFO_MIME_TYPE)) @switch($x=$f->buffer($value,FILEINFO_MIME_TYPE))
@case('image/jpeg') @case('image/jpeg')
@default @default
<td> <td>
<input type="hidden" name="{{ $o->name_lc }}[]" value="{{ md5($value) }}"> <input type="hidden" name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ md5($value) }}">
<img @class(['border','rounded','p-2','m-0','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$loop->index))]) src="data:{{ $x }};base64, {{ base64_encode($value) }}" /> <img alt="{{ $o->dn }}" @class(['border','rounded','p-2','m-0','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$loop->index))]) src="data:{{ $x }};base64, {{ base64_encode($value) }}" />
@if ($edit) @if($edit)
<br> <br>
<!-- @todo TO IMPLEMENT --> <!-- @todo TO IMPLEMENT -->
<button class="btn btn-sm btn-danger deletable d-none mt-3" disabled><i class="fas fa-trash-alt"></i> @lang('Delete')</button> <button class="btn btn-sm btn-danger deletable d-none mt-3" disabled><i class="fas fa-trash-alt"></i> @lang('Delete')</button>

View File

@ -1,10 +1,10 @@
<!-- @todo We are not handling redirect backs yet with updated passwords --> <!-- @todo We are not handling redirect backs yet with updated passwords -->
<!-- $o=KrbPrincipleKey::class --> <!-- $o=KrbPrincipleKey::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) @foreach($o->tagValuesOld($langtag) as $key => $value)
@if($edit) @if($edit)
<div class="input-group has-validation mb-3"> <div class="input-group has-validation mb-3">
<input type="password" @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="{{ md5($value) }}" @readonly(true)> <input type="password" @class(['form-control','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$loop->index)),'mb-1','border-focus'=>! $o->tagValuesOld($langtag)->contains($value)]) name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ md5($value) }}" @readonly(true)>
<div class="invalid-feedback pb-2"> <div class="invalid-feedback pb-2">
@if($e) @if($e)
@ -13,7 +13,7 @@
</div> </div>
</div> </div>
@else @else
{{ str_repeat('*',16) }} {{ $o->render_item_old($langtag.'.'.$key) }}
@endif @endif
@endforeach @endforeach
</x-attribute.layout> </x-attribute.layout>

View File

@ -1,21 +1,21 @@
<!-- $o=KrbTicketFlags::class --> <!-- $o=KrbTicketFlags::class -->
<x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o"> <x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o">
@foreach(($o->values->count() ? $o->values : ($new ? [0] : NULL)) as $value) @foreach(Arr::get(old($o->name_lc,[$langtag=>$o->tagValues($langtag)]),$langtag,[]) as $key => $value)
@if($edit) @if($edit)
<div id="32"></div> <div id="32"></div>
<div id="16"></div> <div id="16"></div>
<div class="input-group has-validation mb-3"> <div class="input-group has-validation mb-3">
<input type="hidden" @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 }}" @readonly(true)> <input type="hidden" name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ $value }}" @readonly(true)>
<div class="invalid-feedback pb-2"> <div class="invalid-feedback pb-2">
@if($e) @if($e=$errors->get($o->name_lc.'.'.$loop->index))
{{ join('|',$e) }} {{ join('|',$e) }}
@endif @endif
</div> </div>
</div> </div>
@else @else
{{ $value }} {{ $o->render_item_old($langtag.'.'.$key) }}
@endif @endif
@endforeach @endforeach
</x-attribute.layout> </x-attribute.layout>

View File

@ -1,12 +1,12 @@
<!-- $o=Attribute::class --> <!-- $o=Attribute/ObjectClass::class -->
<x-attribute.layout :edit="$edit" :new="$new" :o="$o" :langtag="$langtag"> <x-attribute.layout :edit="$edit" :new="$new" :o="$o" :langtag="$langtag">
@foreach(old($o->name_lc,$o->values) as $value) @foreach(Arr::get(old($o->name_lc,[$langtag=>($new ?? FALSE) ? [NULL] : $o->tagValues($langtag)]),$langtag) as $key => $value)
@if($edit) @if($edit)
<x-attribute.widget.objectclass :o="$o" :edit="$edit" :new="$new" :loop="$loop" :value="$value" :langtag="$langtag"/> <x-attribute.widget.objectclass :o="$o" :edit="$edit" :new="$new" :loop="$loop" :value="$value" :langtag="$langtag"/>
@else @else
{{ $value }} {{ $o->render_item_old($langtag.'.'.$key) }}
@if ($o->isStructural($value)) @if ($o->isStructural($value))
<input type="hidden" name="{{ $o->name_lc }}[]" value="{{ $value }}"> <input type="hidden" name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ $value }}">
<span class="float-end">@lang('structural')</span> <span class="float-end">@lang('structural')</span>
@endif @endif
<br> <br>

View File

@ -1,11 +1,11 @@
<!-- @todo We are not handling redirect backs yet with updated passwords --> <!-- @todo We are not handling redirect backs yet with updated passwords -->
<!-- $o=Password::class --> <!-- $o=Password::class -->
<x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o" :langtag="$langtag"> <x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o" :langtag="$langtag">
@foreach($o->values_old as $value) @foreach($o->tagValuesOld($langtag) as $key => $value)
@if($edit) @if($edit)
<div class="input-group has-validation mb-3"> <div class="input-group has-validation mb-3">
<x-form.select id="userpassword_hash_{{$loop->index}}" name="userpassword_hash[]" :value="$o->hash($value)->id()" :options="$helpers" allowclear="false" :disabled="true"/> <x-form.select id="userpassword_hash_{{$loop->index}}" name="userpassword_hash[{{ $langtag }}][]" :value="$o->hash($value)->id()" :options="$helpers" allowclear="false" :disabled="true"/>
<input type="password" @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="{{ md5($value) }}" @readonly(true)> <input type="password" @class(['form-control','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$loop->index)),'mb-1','border-focus'=>! $o->tagValuesOld($langtag)->contains($value)]) name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ md5($value) }}" @readonly(true)>
<div class="invalid-feedback pb-2"> <div class="invalid-feedback pb-2">
@if($e) @if($e)
@ -14,7 +14,7 @@
</div> </div>
</div> </div>
@else @else
{{ (($x=$o->hash($value)) && ($x::id() !== '*clear*')) ? sprintf('{%s}',$x::shortid()) : '' }}{{ str_repeat('*',16) }} {{ $o->render_item_old($langtag.'.'.$key) }}
@endif @endif
@endforeach @endforeach
</x-attribute.layout> </x-attribute.layout>

View File

@ -1,9 +1,9 @@
<span id="objectclass_{{$value}}"> <span id="objectclass_{{$value}}">
<div class="input-group has-validation"> <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 --> <!-- @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','input-group-end','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)> <input type="text" @class(['form-control','input-group-end','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$loop->index)),'mb-1','border-focus'=>! $o->tagValuesOld($langtag)->contains($value)]) name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ $value }}" placeholder="{{ Arr::get($o->values,$loop->index,'['.__('NEW').']') }}" @readonly(true)>
@if ($o->isStructural($value)) @if ($o->isStructural($value))
<span class="input-group-end text-black-50">structural</span> <span class="input-group-end text-black-50">@lang('structural')</span>
@else @else
<span class="input-group-end"><i class="fas fa-fw fa-xmark"></i></span> <span class="input-group-end"><i class="fas fa-fw fa-xmark"></i></span>
@endif @endif

View File

@ -3,7 +3,6 @@
@php($clone=FALSE) @php($clone=FALSE)
<span class="p-0 m-0"> <span class="p-0 m-0">
@if($o->is_rdn) @if($o->is_rdn)
<br/>
<button class="btn btn-sm btn-outline-focus mt-3" disabled><i class="fas fa-fw fa-exchange"></i> @lang('Rename')</button> <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) @elseif($edit && $o->can_addvalues)
@switch(get_class($o)) @switch(get_class($o))
@ -229,8 +228,11 @@
// Create a new entry when Add Value clicked // Create a new entry when Add Value clicked
$('#{{ $o->name }}-addnew.addable').click(function (item) { $('#{{ $o->name }}-addnew.addable').click(function (item) {
var cln = $(this).parent().parent().find('input:last').parent().clone(); var cln = $(this).parent().parent().find('input:last').parent().clone();
cln.find('input:last').attr('value','').attr('placeholder', '[@lang('NEW')]'); cln.find('input:last')
cln.appendTo('#'+item.currentTarget.id.replace('-addnew','')); .attr('value','')
.attr('placeholder', '[@lang('NEW')]')
.addClass('border-focus')
.appendTo('#'+item.currentTarget.id.replace('-addnew',''));
}); });
}); });
</script> </script>

View File

@ -1,8 +1,8 @@
<!-- $o=Attribute::class --> <!-- $o=Attribute::class -->
<x-attribute.layout :edit="false" :new="false" :detail="true" :o="$o"> <x-attribute.layout :edit="false" :new="false" :detail="true" :o="$o">
@foreach(old($o->name_lc,($new ?? FALSE) ? [NULL] : $o->values) as $value) @foreach(Arr::get(old($o->name_lc,[$langtag=>$o->tagValues($langtag)]),$langtag,[]) as $value)
<div class="input-group"> <div class="input-group">
<input type="text" @class(['form-control','mb-1']) name="{{ $o->name_lc }}[]" value="{{ \Carbon\Carbon::createFromTimestamp(strtotime($value))->format(config('pla.datetime_format','Y-m-d H:i:s')) }}" @disabled(true)> <input type="text" class="form-control mb-1" value="{{ \Carbon\Carbon::createFromTimestamp(strtotime($value))->format(config('pla.datetime_format','Y-m-d H:i:s')) }}" disabled>
</div> </div>
@endforeach @endforeach
</x-attribute.layout> </x-attribute.layout>

View File

@ -1,8 +1,8 @@
<!-- $o=Attribute::class --> <!-- $o=Attribute::class -->
<x-attribute.layout :edit="false" :new="false" :detail="true" :o="$o"> <x-attribute.layout :edit="false" :new="false" :detail="true" :o="$o">
@foreach(old($o->name_lc,($new ?? FALSE) ? [NULL] : $o->values) as $value) @foreach(Arr::get(old($o->name_lc,[$langtag=>$o->tagValues($langtag)]),$langtag,[]) as $value)
<div class="input-group"> <div class="input-group">
<input type="text" @class(['form-control','mb-1']) name="{{ $o->name_lc }}[]" value="{{ $value }}" @disabled(true)> <input type="text" class="form-control mb-1" value="{{ $value }}" disabled>
</div> </div>
@endforeach @endforeach
</x-attribute.layout> </x-attribute.layout>

View File

@ -1,5 +1,4 @@
<div class="row"> <div class="row">
<div class="col-12 col-xl-3"> <div class="col-12 col-xl-3">
<select id="attributetype" class="form-control"> <select id="attributetype" class="form-control">
<option value="-all-">-all-</option> <option value="-all-">-all-</option>

View File

@ -1,7 +1,10 @@
@extends('layouts.dn') @extends('layouts.dn')
@section('page_title') @section('page_title')
@include('fragment.dn.header',['o'=>($oo=$server->fetch(old('container',$container)))]) @include('fragment.dn.header',[
'o'=>($oo=$server->fetch(old('container',$container))),
'langtags'=>collect(),
])
@endsection @endsection
@section('main-content') @section('main-content')

View File

@ -1,3 +1,5 @@
@use(App\Ldap\Entry)
@extends('layouts.dn') @extends('layouts.dn')
@section('page_title') @section('page_title')
@ -80,9 +82,6 @@
<ul class="nav nav-tabs mb-0"> <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="#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> <li class="nav-item"><a data-bs-toggle="tab" href="#internal" class="nav-link">@lang('Internal')</a></li>
@env(['local'])
<li class="nav-item"><a data-bs-toggle="tab" href="#debug" class="nav-link">@lang('Debug')</a></li>
@endenv
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
@ -92,14 +91,13 @@
@csrf @csrf
<input type="hidden" name="dn" value=""> <input type="hidden" name="dn" value="">
<div class="card-header border-bottom-0"> <div class="card-header border-bottom-0">
<div class="btn-actions-pane-right"> <div class="btn-actions-pane-right">
<div role="group" class="btn-group-sm nav btn-group"> <div role="group" class="btn-group-sm nav btn-group">
@foreach($langtags->prepend('')->push('+') as $tag) @foreach($langtags->prepend(Entry::TAG_NOTAG)->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"> <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) @switch($tag)
@case('') @case(Entry::TAG_NOTAG)
<i class="fas fa-fw fa-border-none" data-bs-toggle="tooltip" data-bs-custom-class="custom-tooltip" title="@lang('No Lang Tag')"></i> <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 @break
@ -122,9 +120,9 @@
@foreach($langtags as $tag) @foreach($langtags as $tag)
<div class="tab-pane @if(! $loop->index) active @endif" id="tab-lang-{{ $tag ?: '_default' }}" role="tabpanel"> <div class="tab-pane @if(! $loop->index) active @endif" id="tab-lang-{{ $tag ?: '_default' }}" role="tabpanel">
@switch($tag) @switch($tag)
@case('') @case(Entry::TAG_NOTAG)
@foreach ($o->getVisibleAttributes($tag) as $ao) @foreach ($o->getVisibleAttributes($tag) as $ao)
<x-attribute-type :edit="true" :o="$ao" langtag=""/> <x-attribute-type :edit="true" :o="$ao" :langtag="$tag"/>
@endforeach @endforeach
@break @break
@ -157,26 +155,11 @@
</div> </div>
<!-- Internal Attributes --> <!-- Internal Attributes -->
<div class="tab-pane" id="internal" role="tabpanel"> <div class="tab-pane mt-3" id="internal" role="tabpanel">
@foreach ($o->getInternalAttributes() as $ao) @foreach ($o->getInternalAttributes() as $ao)
<x-attribute-type :o="$ao"/> <x-attribute-type :o="$ao"/>
@endforeach @endforeach
</div> </div>
<!-- Debug -->
<div class="tab-pane" id="debug" role="tabpanel">
<div class="row">
<div class="col-4">
@dump($o)
</div>
<div class="col-4">
@dump($o->getAttributes())
</div>
<div class="col-4">
@dump(['available'=>$o->getAvailableAttributes()->pluck('name'),'missing'=>$o->getMissingAttributes()->pluck('name')])
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,7 +1,12 @@
@extends('home') @extends('home')
@section('page_title') @section('page_title')
@include('fragment.dn.header') @include('fragment.dn.header',[
'langtags'=>($langtags=$o->getLangTags()
->flatMap(fn($item)=>$item->values())
->unique()
->sort())
])
@endsection @endsection
@section('main-content') @section('main-content')
@ -23,6 +28,7 @@
<thead> <thead>
<tr> <tr>
<th>Attribute</th> <th>Attribute</th>
<th>Tag</th>
<th>OLD</th> <th>OLD</th>
<th>NEW</th> <th>NEW</th>
</tr> </tr>
@ -31,17 +37,22 @@
<tbody> <tbody>
@foreach ($o->getObjects()->filter(fn($item)=>$item->isDirty()) as $key => $oo) @foreach ($o->getObjects()->filter(fn($item)=>$item->isDirty()) as $key => $oo)
<tr> <tr>
<th rowspan="{{ $x=max($oo->values->keys()->max(),$oo->values_old->keys()->max())+1}}"> <th rowspan="{{ $x=max($oo->values->dot()->keys()->count(),$oo->values_old->dot()->keys()->count())+1}}">
<abbr title="{{ $oo->description }}">{{ $oo->name }}</abbr> <abbr title="{{ $oo->description }}">{{ $oo->name }}</abbr>
</th> </th>
@for($xx=0;$xx<$x;$xx++)
@if($xx) @foreach($oo->values->dot()->keys()->merge($oo->values_old->dot()->keys())->unique() as $dotkey)
@if($loop->index)
</tr><tr> </tr><tr>
@endif @endif
<td>{{ (($r=$oo->render_item_old($xx)) !== NULL) ? $r : '['.strtoupper(__('New Value')).']' }}</td> <th>
<td>{{ (($r=$oo->render_item_new($xx)) !== NULL) ? $r : '['.strtoupper(__('Deleted')).']' }}<input type="hidden" name="{{ $key }}[]" value="{{ Arr::get($oo->values,$xx) }}"></td> {{ $dotkey }}
@endfor </th>
<td>{{ (($r=$oo->render_item_old($dotkey)) !== NULL) ? $r : '['.strtoupper(__('New Value')).']' }}</td>
<td>{{ (($r=$oo->render_item_new($dotkey)) !== NULL) ? $r : '['.strtoupper(__('Deleted')).']' }}<input type="hidden" name="{{ $key }}[{{ collect(explode('.',$dotkey))->first() }}][]" value="{{ Arr::get($oo,$dotkey) }}"></td>
@endforeach
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>