From bbfc53e749aa59c863ef3b259e5274bd2c1be7e7 Mon Sep 17 00:00:00 2001
From: Thibault Dockx <thibault.dockx@fusiondirectory.org>
Date: Thu, 20 Mar 2025 12:04:03 +0000
Subject: [PATCH] :sparkles: feat(zimbra) improve account caching and update
 methods for better reliability - step 1 - no backend config yet

---
 .../class_mail-methods-zimbra.inc             | 210 ++++++++++--------
 1 file changed, 114 insertions(+), 96 deletions(-)

diff --git a/zimbra/personal/mail/mail-methods/class_mail-methods-zimbra.inc b/zimbra/personal/mail/mail-methods/class_mail-methods-zimbra.inc
index a5ef508f4d..49d0dd1297 100644
--- a/zimbra/personal/mail/mail-methods/class_mail-methods-zimbra.inc
+++ b/zimbra/personal/mail/mail-methods/class_mail-methods-zimbra.inc
@@ -250,33 +250,39 @@ class mailMethodZimbra extends mailMethod
     return $this->cacheAccount();
   }
 
-  private function cacheAccount ()
+  private function cacheAccount(): bool
   {
-    if (empty($this->account_id)) {
-      return FALSE;
-    } elseif (isset($this->cachedAccount['account']) && ($this->account_id == $this->cachedAccount['id'])) {
-      return TRUE;
-    }
-    if ($this->type == 'user') {
-      $command    = 'GetAccount';
-      $answerkey  = 'account';
-    } else {
-      $command    = 'GetGroup';
-      $answerkey  = 'group';
-    }
-    $answer = $this->query($command, ['name' => $this->account_id], ['account.NO_SUCH_ACCOUNT']);
-    if (($answer !== FALSE) && isset($answer[$answerkey])) {
-      $this->cachedAccount = [
-        'account'   => $answer[$answerkey],
-        'id'        => $this->account_id,
-        'zimbraId'  => $answer[$answerkey]['zimbraId'],
-        'time'      => time()
-      ];
-      $this->cleanCachedAccountArrays();
-      return TRUE;
-    } else {
-      return FALSE;
-    }
+      if (empty($this->account_id)) {
+          return FALSE;
+      } elseif (isset($this->cachedAccount['account']) && ($this->account_id == $this->cachedAccount['id'])) {
+          return TRUE;
+      }
+  
+      // Determine the command based on the type (user or group)
+      if ($this->type == 'user') {
+          $command    = 'GetAccount';
+          $answerKey  = 'account';
+      } else {
+          $command    = 'GetGroup';
+          $answerKey  = 'group';
+      }
+  
+      // Query Zimbra for the account information
+      $answer = $this->query($command, ['name' => $this->account_id], ['account.NO_SUCH_ACCOUNT']);
+      if (($answer !== FALSE) && isset($answer[$answerKey])) {
+          $this->cachedAccount = [
+              'account'   => $answer[$answerKey],
+              'id'        => $this->account_id,
+              'zimbraId'  => $answer[$answerKey]['zimbraId'],
+              'time'      => time()
+          ];
+  
+          // Ensure aliases are properly formatted as an array
+          $this->cleanCachedAccountArrays();
+          return TRUE;
+      } else {
+          return FALSE;
+      }
   }
 
   /*
@@ -424,46 +430,49 @@ class mailMethodZimbra extends mailMethod
     return $this->query($command, $account);
   }
 
-  private function updateAccount (array $infos)
+  private function updateAccount(array $infos): bool
   {
-    /* Step 1 - We fill $account */
-    $account = $this->getAccountArray($infos);
-    unset($account['name']);
-
-    /* Step 2 - We compute diff from server data */
-    $this->cacheAccount();
-    if ($this->is_error()) {
-      return FALSE;
-    }
-    foreach ($account as $key => $value) {
-      if (isset($this->cachedAccount['account'][$key])) {
-        if (is_array($value) && !is_array($this->cachedAccount['account'][$key])) {
-          $this->cachedAccount['account'][$key] = [$this->cachedAccount['account'][$key]];
-        }
-        if ($this->cachedAccount['account'][$key] == $value) {
-          unset($account[$key]);
-        }
-      } elseif (empty($value)) {
-        unset($account[$key]);
+      // Step 1 - Retrieve the current account information
+      $this->cacheAccount();
+      if ($this->is_error()) {
+          return FALSE;
       }
-    }
-
-    /* Step 3 - We send update order if diff is not empty */
-    if (!empty($account)) {
-      $account['zimbraId'] = $this->cachedAccount['zimbraId'];
-      if ($this->type == 'user') {
-        $command = 'ModifyAccount';
-      } else {
-        $command = 'ModifyGroup';
+  
+      // Step 2 - Prepare the account update payload
+      $account = $this->getAccountArray($infos);
+      unset($account['name']); // Remove the name field as it is not needed for updates
+  
+      // Step 3 - Include existing aliases and domains in the update
+      if (isset($this->cachedAccount['account']['zimbraMailAlias'])) {
+          $account['zimbraMailAlias'] = $this->cachedAccount['account']['zimbraMailAlias'];
+      }
+  
+      // Step 4 - Compute the diff between the current state and the desired state
+      foreach ($account as $key => $value) {
+          if (isset($this->cachedAccount['account'][$key])) {
+              if (is_array($value) && !is_array($this->cachedAccount['account'][$key])) {
+                  $this->cachedAccount['account'][$key] = [$this->cachedAccount['account'][$key]];
+              }
+              if ($this->cachedAccount['account'][$key] == $value) {
+                  unset($account[$key]);
+              }
+          } elseif (empty($value)) {
+              unset($account[$key]);
+          }
       }
-      $answer = $this->query($command, $account);
-
-      if ($answer === FALSE) {
-        return FALSE;
+  
+      // Step 5 - Send the update request if there are changes
+      if (!empty($account)) {
+          $account['zimbraId'] = $this->cachedAccount['zimbraId'];
+          $command = ($this->type == 'user') ? 'ModifyAccount' : 'ModifyGroup';
+          $answer = $this->query($command, $account);
+  
+          if ($answer === FALSE) {
+              return FALSE;
+          }
       }
-    }
 
-    /* Step 4 - Call SetPassword for password change if needed */
+      /* Step 6 - Call SetPassword for password change if needed */
     if (($this->type == 'user') && $infos['password_sync']) {
       $userTab = $this->parent->parent->getBaseObject();
       if ($userTab->attributesAccess['userPassword']->getClear() != '') {
@@ -478,48 +487,57 @@ class mailMethodZimbra extends mailMethod
         }
       }
     }
-
-    return TRUE;
+  
+      return TRUE;
   }
 
-  private function setAliases (array $aliases): bool
+  private function setAliases(array $aliases): bool
   {
-    $initialAliases = ($this->cachedAccount['account']['zimbraMailAlias'] ?? []);
-    $remove = array_diff($initialAliases, $aliases);
-    $add    = array_diff($aliases, $initialAliases);
-    if ($this->type == 'user') {
-      $command = 'RemoveAccountAlias';
-    } else {
-      $command = 'RemoveDistributionListAlias';
-    }
-    foreach ($remove as $alias) {
-      if ($alias === '') {
-        continue;
+      // Retrieve existing aliases from the cached account
+      $initialAliases = ($this->cachedAccount['account']['zimbraMailAlias'] ?? []);
+      $allAliases = array_unique(array_merge($initialAliases, $aliases)); // Merge existing and new aliases
+  
+      // Compute the aliases to add and remove
+      $remove = array_diff($initialAliases, $aliases);
+      $add    = array_diff($aliases, $initialAliases);
+  
+      // Remove aliases
+      if ($this->type == 'user') {
+          $command = 'RemoveAccountAlias';
+      } else {
+          $command = 'RemoveDistributionListAlias';
       }
-      $answer = $this->query(
-        $command,
-        ['zimbraId' => $this->cachedAccount['zimbraId'], 'alias' => $alias],
-        ['account.NO_SUCH_DOMAIN','account.NO_SUCH_ALIAS']
-      );
-      if ($this->is_error()) {
-        return FALSE;
+      foreach ($remove as $alias) {
+          if ($alias === '') {
+              continue;
+          }
+          $answer = $this->query(
+              $command,
+              ['zimbraId' => $this->cachedAccount['zimbraId'], 'alias' => $alias],
+              ['account.NO_SUCH_DOMAIN', 'account.NO_SUCH_ALIAS']
+          );
+          if ($this->is_error()) {
+              return FALSE;
+          }
       }
-    }
-    if ($this->type == 'user') {
-      $command = 'AddAccountAlias';
-    } else {
-      $command = 'AddDistributionListAlias';
-    }
-    foreach ($add as $alias) {
-      if ($alias === '') {
-        continue;
+  
+      // Add aliases
+      if ($this->type == 'user') {
+          $command = 'AddAccountAlias';
+      } else {
+          $command = 'AddDistributionListAlias';
       }
-      $answer = $this->query($command, ['zimbraId' => $this->cachedAccount['zimbraId'], 'alias' => $alias]);
-      if ($answer === FALSE) {
-        return FALSE;
+      foreach ($add as $alias) {
+          if ($alias === '') {
+              continue;
+          }
+          $answer = $this->query($command, ['zimbraId' => $this->cachedAccount['zimbraId'], 'alias' => $alias]);
+          if ($answer === FALSE) {
+              return FALSE;
+          }
       }
-    }
-    return TRUE;
+  
+      return TRUE;
   }
 
   private function updateMembers ()
-- 
GitLab