From 4b9a162696e9ea16eec6bc3eb71e316a4dfefae8 Mon Sep 17 00:00:00 2001
From: Dave Mc Nicoll <info@mcnd.ca>
Date: Wed, 24 Apr 2024 11:25:48 -0400
Subject: [PATCH] - QueryFragmentInterface fixes

---
 src/Common/LdapObject.php | 53 ++++++++++++++++++++++++++++++++-------
 src/Entity/User.php       | 36 ++++++++++++++++++++++++--
 src/Query/Set.php         |  2 +-
 src/QueryBuilder.php      | 10 ++++----
 src/Repository.php        |  4 +--
 5 files changed, 86 insertions(+), 19 deletions(-)

diff --git a/src/Common/LdapObject.php b/src/Common/LdapObject.php
index 777e213..73684a9 100644
--- a/src/Common/LdapObject.php
+++ b/src/Common/LdapObject.php
@@ -2,6 +2,7 @@
 
 namespace Ulmus\Ldap\Common;
 
+use http\Exception\InvalidArgumentException;
 use function ldap_set_option, ldap_start_tls, ldap_bind, ldap_unbind, ldap_connect, ldap_close, ldap_get_entries, ldap_mod_replace, ldap_count_entries, ldap_errno, ldap_error;
 
 class LdapObject {
@@ -12,6 +13,8 @@ class LdapObject {
 
     public $search;
 
+    protected string $host;
+
     public int $rowCount = 0;
 
     public int $bufferedRows = 0;
@@ -22,6 +25,8 @@ class LdapObject {
 
     public bool $binded = false;
 
+    public bool $deleteRecursively = true;
+
     public function __destruct()
     {
         isset($this->connection) and ldap_close($this->connection);
@@ -29,6 +34,8 @@ class LdapObject {
 
     public function connect(string $host, string $baseDn) : bool
     {
+        $this->host = $host;
+
         $this->connection = ldap_connect("ldap://$host");
 
         $this->dn = $baseDn;
@@ -54,7 +61,12 @@ class LdapObject {
             throw new \Exception("LdapObject is already binded with a user. Use the unbind() method to release it.");
         }
 
-        $this->binded = ldap_bind($this->connection, $dn, $password);
+        try {
+            $this->binded = ldap_bind($this->connection, $dn, $password);
+        }
+        catch(\Throwable $ex) {
+            throw new \ErrorException(sprintf("%s [ using: %s on %s ]", $ex->getMessage(), $dn, $this->host ));
+        }
 
         return $this->binded;
     }
@@ -82,7 +94,9 @@ class LdapObject {
     {
         static::$dump && call_user_func_array(static::$dump, [ $filter, $fields ]);
 
-        $this->search = ldap_search($this->connection, $this->dn, $filter['filters'], $filter['fields'] ?? [], 0, 0);
+        $this->search = @ldap_search($this->connection, $this->dn, $filter['filters'], $filter['fields'] ?? [], 0, $filter['limit'] ?? 0);
+
+        $this->throwLdapException();
 
         $this->rowCount = $this->bufferedRows = ldap_count_entries($this->connection, $this->search);
 
@@ -127,10 +141,10 @@ class LdapObject {
         return false;
     }
 
-    public function fetchAll()
-    {
-        return ldap_get_entries($this->connection, $result);
-    }
+    #public function fetchAll()
+    #{
+    #    return ldap_get_entries($this->connection, $result);
+    #}
 
     public function rowCount() : int
     {
@@ -187,9 +201,22 @@ class LdapObject {
         return $this;
     }
 
-    public function runDeleteQuery(array $filter, array $control)
+    public function runDeleteQuery(array $filter)
     {
-        static::$dump && call_user_func_array(static::$dump, [ $filter, $dataset ]);
+        static::$dump && call_user_func_array(static::$dump, [ $filter ]);
+
+        if ( empty($filter['dn']) ) {
+            throw new InvalidArgumentException("A valid DN must be provided to run a 'delete' query on LDAP connector");
+        }
+
+        if ($this->deleteRecursively) {
+            $list = ldap_list($this->connection, $filter['dn'], "ObjectClass=*", [""]);
+            $info = ldap_get_entries($this->connection, $list);
+
+            for($i=0; $i < $info['count']; $i++){
+                $this->runDeleteQuery($info[$i]);
+            }
+        }
 
         if ( false === ( $queryResult = ldap_delete($this->connection, $filter['dn']) ) ) {
             $this->throwLdapException();
@@ -202,7 +229,15 @@ class LdapObject {
 
     protected function throwLdapException() : void
     {
-        throw new \Exception(sprintf('LDAP error #%s `%s`', ldap_errno($this->connection), ldap_error($this->connection)));
+        $no = ldap_errno($this->connection);
+
+        switch($no) {
+            case 0: # Success
+            case 4: # Skipping 'Size limit exceeded' error
+                return;
+        }
+
+        throw new \Exception(sprintf('LDAP error #%s `%s`', $no, ldap_error($this->connection)));
     }
 
     public function closeCursor() : void {}
diff --git a/src/Entity/User.php b/src/Entity/User.php
index 6267534..ab74f3e 100644
--- a/src/Entity/User.php
+++ b/src/Entity/User.php
@@ -61,9 +61,12 @@ class User
     #[Field]
     public string $title;
 
-    ##[Virtual]
+    ##[Field(readonly:true)]
     # public ? array $memberOf;
 
+    ##[Field(readonly:true)]
+    #public ? array $proxyAddresses;
+
     #[Field]
     public int $userAccountControl;
 
@@ -132,7 +135,13 @@ class User
 
     #[Field(name: "lastLogon", readonly: true)]
     public LdapDatetime $lastLogon;
- 
+
+    #[Field(name: "whenCreated", readonly: true)]
+    public LdapDatetime $whenCreated;
+
+    #[Field(name: "whenChanged", readonly: true)]
+    public LdapDatetime $whenChanged;
+
     public function __toString() : string
     {
         return implode(' ', array_filter([ $this->firstname ?? "", $this->lastname ?? "" ])) ?: ( $this->displayName ?? "" );
@@ -145,4 +154,27 @@ class User
 
         return $arr;
     }
+
+    public function brokenMigration() : bool
+    {
+        if ( empty($this->targetAddress) ) {
+            foreach($this->proxyAddresses ?? [] as $proxy) {
+                if ( str_contains(strtolower($proxy), 'x500') ) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    public function migrated() : bool
+    {
+        return ! empty($this->targetAddress);
+    }
+
+    public function unmigrated() : bool
+    {
+        return empty($this->targetAddress) && empty($this->proxyAddresses);
+    }
 }
\ No newline at end of file
diff --git a/src/Query/Set.php b/src/Query/Set.php
index abe4c78..21b46e8 100644
--- a/src/Query/Set.php
+++ b/src/Query/Set.php
@@ -4,7 +4,7 @@ namespace Ulmus\Ldap\Query;
 
 class Set extends \Ulmus\Query\Set
 {
-    public function render() /* : mixed */
+    public function render() : mixed
     {
         foreach($this->dataset as $key => $value) {
             $this->queryBuilder->addParameter($value, $key);
diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php
index 549f5dd..c0f75c8 100644
--- a/src/QueryBuilder.php
+++ b/src/QueryBuilder.php
@@ -4,7 +4,7 @@ namespace Ulmus\Ldap;
 
 use Ulmus;
 
-class QueryBuilder implements Ulmus\Query\QueryBuilderInterface
+class QueryBuilder implements Ulmus\QueryBuilder\QueryBuilderInterface
 {
     public Query\Filter $where;
 
@@ -20,7 +20,7 @@ class QueryBuilder implements Ulmus\Query\QueryBuilderInterface
      * Those values are to be inserted or updated
      */
     public array $values = [];
-    
+
     public string $whereConditionOperator = Ulmus\Query\Where::CONDITION_AND;
     
     public string $havingConditionOperator = Ulmus\Query\Where::CONDITION_AND;
@@ -195,14 +195,14 @@ class QueryBuilder implements Ulmus\Query\QueryBuilderInterface
         return $this;
     }
 
-    public function push(Ulmus\Query\Fragment $queryFragment) : self
+    public function push(Ulmus\Query\QueryFragmentInterface $queryFragment) : self
     {
         $this->queryStack[] = $queryFragment;
         
         return $this;
     }
     
-    public function pull(Ulmus\Query\Fragment $queryFragment) : self
+    public function pull(Ulmus\Query\QueryFragmentInterface $queryFragment) : self
     {
         return array_shift($this->queryStack);
     }
@@ -241,7 +241,7 @@ class QueryBuilder implements Ulmus\Query\QueryBuilderInterface
         return null;
     }
 
-    public function removeFragment(array|string|Ulmus\Query\Fragment|\Stringable $fragment) : void
+    public function removeFragment(array|string|Ulmus\Query\QueryFragmentInterface|\Stringable $fragment) : void
     {
         foreach($this->queryStack as $key => $item) {
             if ( $item === $fragment ) {
diff --git a/src/Repository.php b/src/Repository.php
index 09be466..34c280e 100644
--- a/src/Repository.php
+++ b/src/Repository.php
@@ -91,9 +91,9 @@ class Repository extends \Ulmus\Repository
             throw new \Exception("Your entity class `" . get_class($entity) . "` cannot match entity type of repository `{$this->entityClass}`");
         }
 
-        $this->delete($entity->dn)->deleteAll()->rowCount();
+        $deleted = $this->delete($entity->dn)->deleteAll()->rowCount();
 
-        return false;
+        return $deleted > 0;
     }
 
     public function loadAllFromOU(string $ou) : EntityCollection