dn = $dn; $this->name = $name; $this->_values = collect($values); $this->_values_old = collect($values); $this->oc = collect($oc); $this->schema = (new Server) ->schema('attributetypes',$name); /* # Should this attribute be hidden if ($server->isAttrHidden($this->name)) $this->forcehide = true; # Should this attribute value be read only if ($server->isAttrReadOnly($this->name)) $this->readonly = true; # Should this attribute value be unique if ($server->isAttrUnique($this->name)) $this->unique = true; */ } public function __call(string $name,array $arguments) { abort(555,'Method not handled: '.$name); } public function __get(string $key): mixed { return match ($key) { // List all the attributes 'attributes' => $this->attributes(), // Can this attribute have more values 'can_addvalues' => $this->schema && (! $this->schema->is_single_value) && ((! $this->max_values_count) || ($this->values->count() < $this->max_values_count)), // Schema attribute description 'description' => $this->schema ? $this->schema->{$key} : NULL, // Attribute hints 'hints' => $this->hints(), // Can this attribute be edited 'is_editable' => $this->schema ? $this->schema->{$key} : NULL, // Objectclasses that required this attribute for an LDAP entry 'required' => $this->required(), // Is this attribute an RDN attribute 'is_rdn' => $this->isRDN(), // We prefer the name as per the schema if it exists 'name' => $this->schema ? $this->schema->{$key} : $this->{$key}, // Attribute name in lower case 'name_lc' => strtolower($this->name), // Required by Object Classes 'required_by' => $this->schema?->required_by_object_classes ?: collect(), // Used in Object Classes 'used_in' => $this->schema?->used_in_object_classes ?: collect(), // The current attribute values 'values' => $this->no_attr_tags ? $this->tagValues() : $this->_values, // The original attribute values 'values_old' => $this->no_attr_tags ? $this->tagValuesOld() : $this->_values_old, default => throw new \Exception('Unknown key:' . $key), }; } public function __set(string $key,mixed $values): void { switch ($key) { case 'values': $this->_values = $values; break; case 'values_old': $this->_values_old = $values; break; default: throw new \Exception('Unknown key:'.$key); } } public function __toString(): string { return $this->name; } /* INTERFACE */ public function count(): int { return $this->_values->dot()->count(); } public function offsetExists(mixed $offset): bool { return $this->_values->dot()->has($offset); } public function offsetGet(mixed $offset): mixed { return $this->_values->dot()->get($offset); } public function offsetSet(mixed $offset, mixed $value): void { // We cannot set new values using array syntax } public function offsetUnset(mixed $offset): void { // 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 * * @return Collection */ public function hints(): Collection { $result = collect(); if ($this->is_internal) return $result; // Is this Attribute an RDN if ($this->is_rdn) $result->put(__('rdn'),__('This attribute is required for the RDN')); // If this attribute name is an alias for the schema attribute name // @todo if ($this->required()->count()) $result->put(__('required'),sprintf('%s: %s',__('Required Attribute by ObjectClass(es)'),$this->required()->join(', '))); // If this attribute is a dynamic attribute if ($this->isDynamic()) $result->put(__('dynamic'),__('These are dynamic values present as a result of another attribute')); return $result; } /** * Determine if this attribute has changes * * @return bool */ public function isDirty(): bool { return (($a=$this->values_old->dot()->filter())->keys()->count() !== ($b=$this->values->dot()->filter())->keys()->count()) || ($a->count() !== $b->count()) || ($a->diff($b)->count() !== 0); } /** * Are these values as a result of a dynamic attribute * * @return bool */ public function isDynamic(): bool { return $this->schema->used_in_object_classes ->keys() ->intersect($this->schema->heirachy($this->oc)) ->count() === 0; } /** * Work out if this attribute is an RDN attribute * * @return bool */ public function isRDN(): bool { // If we dont have an DN, then we cant know if (! $this->dn) return FALSE; $rdns = collect(explode('+',substr($this->dn,0,strpos($this->dn,',')))); return $rdns->filter(fn($item) => str_starts_with($item,$this->name.'='))->count() > 0; } /** * Display the attribute value * * @param bool $edit Render an edit form * @param bool $old Use old value * @param bool $new Enable adding values * @return View */ public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View { $view = match ($this->schema->syntax_oid) { self::SYNTAX_CERTIFICATE => view('components.syntax.certificate'), self::SYNTAX_CERTIFICATE_LIST => view('components.syntax.certificatelist'), default => view()->exists($x = 'components.attribute.' . $this->name_lc) ? view($x) : view('components.attribute'), }; return $view ->with('o',$this) ->with('edit',$edit) ->with('old',$old) ->with('new',$new); } public function render_item_old(string $dotkey): ?string { return match ($this->schema->syntax_oid) { self::SYNTAX_CERTIFICATE => join("\n",str_split(base64_encode(Arr::get($this->values_old->dot(),$dotkey)),80)), self::SYNTAX_CERTIFICATE_LIST => join("\n",str_split(base64_encode(Arr::get($this->values_old->dot(),$dotkey)),80)), default => Arr::get($this->values_old->dot(),$dotkey), }; } public function render_item_new(string $dotkey): ?string { return Arr::get($this->values->dot(),$dotkey); } /** * Work out if this attribute is required by an objectClass the entry has * * @return Collection */ public function required(): Collection { // If we dont have any objectclasses then we cant know if it is required return $this->oc->count() ? $this->oc->intersect($this->required_by->keys())->sort() : collect(); } public function tagValues(string $tag=Entry::TAG_NOTAG): Collection { return collect($this->_values ->filter(fn($item,$key)=>($key===$tag)) ->get($tag,[])); } public function tagValuesOld(string $tag=Entry::TAG_NOTAG): Collection { return collect($this->_values_old ->filter(fn($item,$key)=>($key===$tag)) ->get($tag,[])); } }