diff --git a/app/Classes/LDAP/Attribute.php b/app/Classes/LDAP/Attribute.php
index afa04bf..b4857d7 100644
--- a/app/Classes/LDAP/Attribute.php
+++ b/app/Classes/LDAP/Attribute.php
@@ -10,7 +10,7 @@ use App\Classes\LDAP\Schema\AttributeType;
/**
* Represents an attribute of an LDAP Object
*/
-class Attribute
+class Attribute implements \Countable, \ArrayAccess
{
// Attribute Name
protected string $name;
@@ -41,6 +41,9 @@ class Attribute
// RFC3866 Language Tags
protected Collection $lang_tags;
+ // The old values for this attribute - helps with isDirty() to determine if there is an update pending
+ protected Collection $oldValues;
+
/*
# Has the attribute been modified
protected $modified = false;
@@ -98,6 +101,7 @@ class Attribute
$this->values = collect($values);
$this->lang_tags = collect();
$this->required_by = collect();
+ $this->oldValues = collect();
// No need to load our schema for internal attributes
if (! $this->is_internal)
@@ -151,6 +155,31 @@ class Attribute
return $this->__get('name');
}
+ public function count(): int
+ {
+ return $this->values->count();
+ }
+
+ public function offsetExists(mixed $offset): bool
+ {
+ return ! is_null($this->values->get($offset));
+ }
+
+ public function offsetGet(mixed $offset): mixed
+ {
+ return $this->values->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
+ }
+
/**
* Return the hints about this attribute, ie: RDN, Required, etc
*
@@ -179,6 +208,24 @@ class Attribute
return $result->toArray();
}
+ /**
+ * Determine if this attribute has changes
+ *
+ * @return bool
+ */
+ public function isDirty(): bool
+ {
+ if ($this->oldValues->count() !== $this->values->count())
+ return TRUE;
+
+ return $this->values->diff($this->oldValues)->count() !== 0;
+ }
+
+ public function oldValues(array $array): void
+ {
+ $this->oldValues = collect($array);
+ }
+
/**
* Display the attribute value
*
@@ -190,6 +237,7 @@ class Attribute
{
return view('components.attribute')
->with('edit',$edit)
+ ->with('new',FALSE)
->with('o',$this);
}
diff --git a/app/Classes/LDAP/Attribute/Binary/JpegPhoto.php b/app/Classes/LDAP/Attribute/Binary/JpegPhoto.php
index 13910f2..a9ae277 100644
--- a/app/Classes/LDAP/Attribute/Binary/JpegPhoto.php
+++ b/app/Classes/LDAP/Attribute/Binary/JpegPhoto.php
@@ -5,12 +5,15 @@ namespace App\Classes\LDAP\Attribute\Binary;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute\Binary;
+use App\Traits\MD5Updates;
/**
* Represents an JpegPhoto Attribute
*/
final class JpegPhoto extends Binary
{
+ use MD5Updates;
+
public function __construct(string $name,array $values)
{
parent::__construct($name,$values);
diff --git a/app/Classes/LDAP/Attribute/Password.php b/app/Classes/LDAP/Attribute/Password.php
index d1842b1..1a64c9c 100644
--- a/app/Classes/LDAP/Attribute/Password.php
+++ b/app/Classes/LDAP/Attribute/Password.php
@@ -5,12 +5,15 @@ namespace App\Classes\LDAP\Attribute;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute;
+use App\Traits\MD5Updates;
/**
* Represents an attribute whose values are passwords
*/
final class Password extends Attribute
{
+ use MD5Updates;
+
public function render(bool $edit=FALSE,bool $blank=FALSE): View
{
return view('components.attribute.password')
diff --git a/app/Ldap/Entry.php b/app/Ldap/Entry.php
index 5f55bb3..b974232 100644
--- a/app/Ldap/Entry.php
+++ b/app/Ldap/Entry.php
@@ -15,6 +15,100 @@ class Entry extends Model
/* OVERRIDES */
public function getAttributes(): array
+ {
+ return $this->getAttributesAsObjects()->toArray();
+ }
+
+ /**
+ * Determine if the new and old values for a given key are equivalent.
+ */
+ protected function originalIsEquivalent(string $key): bool
+ {
+ if (! array_key_exists($key, $this->original)) {
+ return false;
+ }
+
+ $current = $this->attributes[$key];
+ $original = $this->original[$key];
+
+ if ($current === $original) {
+ return true;
+ }
+
+ //dump(['key'=>$key,'current'=>$current,'original'=>$this->original[$key],'objectvalue'=>$this->getAttributeAsObject($key)->isDirty()]);
+ return ! $this->getAttributeAsObject($key)->isDirty();
+ }
+
+ public function getOriginal(): array
+ {
+ static $result = NULL;
+
+ if (is_null($result)) {
+ $result = collect();
+
+ // @todo Optimise this foreach with getAttributes()
+ foreach (parent::getOriginal() as $attribute => $value) {
+ // If the attribute name has language tags
+ $matches = [];
+ if (preg_match('/^([a-zA-Z]+)(;([a-zA-Z-;]+))+/',$attribute,$matches)) {
+ $attribute = $matches[1];
+
+ // If the attribute doesnt exist we'll create it
+ $o = Arr::get($result,$attribute,Factory::create($attribute,[]));
+ $o->setLangTag($matches[3],$value);
+
+ } else {
+ $o = Factory::create($attribute,$value);
+ }
+
+ if (! $result->has($attribute)) {
+ // Set the rdn flag
+ if (preg_match('/^'.$attribute.'=/i',$this->dn))
+ $o->setRDN();
+
+ // Set required flag
+ $o->required_by(collect($this->getAttribute('objectclass')));
+
+ $result->put($attribute,$o);
+ }
+ }
+ }
+
+ return $result->toArray();
+ }
+
+ /* ATTRIBUTES */
+
+ /**
+ * Return a key to use for sorting
+ *
+ * @todo This should be the DN in reverse order
+ * @return string
+ */
+ public function getSortKeyAttribute(): string
+ {
+ return $this->getDn();
+ }
+
+ /* METHODS */
+
+ /**
+ * Get an attribute as an object
+ *
+ * @param string $key
+ * @return Attribute|null
+ */
+ public function getAttributeAsObject(string $key): Attribute|null
+ {
+ return Arr::get($this->getAttributesAsObjects(),$key);
+ }
+
+ /**
+ * Convert all our attribute values into an array of Objects
+ *
+ * @return Collection
+ */
+ protected function getAttributesAsObjects(): Collection
{
static $result = NULL;
@@ -43,6 +137,9 @@ class Entry extends Model
// Set required flag
$o->required_by(collect($this->getAttribute('objectclass')));
+ // Store our original value to know if this attribute has changed
+ $o->oldValues(Arr::get($this->original,$attribute));
+
$result->put($attribute,$o);
}
}
@@ -73,36 +170,12 @@ class Entry extends Model
// Case where at least one attribute or its friendly name is in $attrs_display_order
// return -1 if $a before $b in $attrs_display_order
return ($a_key < $b_key) ? -1 : 1;
- } ])->toArray();
+ } ]);
}
return $result;
}
- /* ATTRIBUTES */
-
- /**
- * Return a key to use for sorting
- *
- * @todo This should be the DN in reverse order
- * @return string
- */
- public function getSortKeyAttribute(): string
- {
- return $this->getDn();
- }
-
- /* METHODS */
-
- /**
- * Return a secure version of the DN
- * @return string
- */
- public function getDNSecure(): string
- {
- return Crypt::encryptString($this->getDn());
- }
-
/**
* Return a list of available attributes - as per the objectClass entry of the record
*
@@ -118,6 +191,15 @@ class Entry extends Model
return $result;
}
+ /**
+ * Return a secure version of the DN
+ * @return string
+ */
+ public function getDNSecure(): string
+ {
+ return Crypt::encryptString($this->getDn());
+ }
+
/**
* Return a list of LDAP internal attributes
*
diff --git a/app/Traits/MD5Updates.php b/app/Traits/MD5Updates.php
new file mode 100644
index 0000000..85c442d
--- /dev/null
+++ b/app/Traits/MD5Updates.php
@@ -0,0 +1,23 @@
+values->diff($this->oldValues) as $key => $value)
+ if (md5(Arr::get($this->oldValues,$key)) !== $value)
+ return TRUE;
+
+ return FALSE;
+ }
+}
\ No newline at end of file
diff --git a/resources/views/components/attribute/binary/jpegphoto.blade.php b/resources/views/components/attribute/binary/jpegphoto.blade.php
index 13e8421..494e9c2 100644
--- a/resources/views/components/attribute/binary/jpegphoto.blade.php
+++ b/resources/views/components/attribute/binary/jpegphoto.blade.php
@@ -1,33 +1,40 @@
-@if($edit)
-