Skip to content

Commit

Permalink
Merge pull request #1427 from IrAlfred/fix-subfolders-display-issue-w…
Browse files Browse the repository at this point in the history
…ith-xeams

fix(other): subfolders not appearing in IMAP accounts with XEAMS mail server
  • Loading branch information
kroky authored Jan 21, 2025
2 parents 84232a2 + 562b349 commit 8447c85
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 7 deletions.
2 changes: 1 addition & 1 deletion modules/core/hm-mailbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ public function get_folders($only_subscribed = false) {
return;
}
if ($this->is_imap()) {
return $this->connection->get_mailbox_list($only_subscribed);
return $this->connection->get_mailbox_list($only_subscribed, children_capability: $this->connection->server_support_children_capability());
} else {
return $this->connection->get_folders(null, $only_subscribed, $this->user_config->get('unsubscribed_folders')[$this->server_id] ?? []);
}
Expand Down
9 changes: 7 additions & 2 deletions modules/imap/hm-imap-parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ protected function check_mailbox_state_change($attributes, $cached_state=false,
* @param bool $lsub flag to use LSUB
* @return array IMAP LIST/LSUB commands
*/
protected function build_list_commands($lsub, $mailbox, $keyword) {
protected function build_list_commands($lsub, $mailbox, $keyword, $children_capability=true) {
$commands = array();
if ($lsub) {
$imap_command = 'LSUB';
Expand Down Expand Up @@ -297,7 +297,12 @@ protected function build_list_commands($lsub, $mailbox, $keyword) {
else {
$status = '';
}
$commands[] = array($imap_command.' "'.$namespace."\" \"$keyword\"$status\r\n", $namespace);

if (!$children_capability && $namespace) {
$commands[] = array($imap_command.' " " "'.$namespace."$keyword\"$status\r\n", $namespace);
} else {
$commands[] = array($imap_command.' "'.$namespace."\" \"$keyword\"$status\r\n", $namespace);
}
}
return $commands;
}
Expand Down
115 changes: 111 additions & 4 deletions modules/imap/hm-imap.php
Original file line number Diff line number Diff line change
Expand Up @@ -526,14 +526,15 @@ public function get_special_use_mailboxes($type=false) {
* @param bool $lsub flag to limit results to subscribed folders only
* @return array associative array of folder details
*/
public function get_mailbox_list($lsub=false, $mailbox='', $keyword='*') {
public function get_mailbox_list($lsub=false, $mailbox='', $keyword='*', $children_capability=true) {
/* defaults */
$folders = array();
$excluded = array();
$parents = array();
$delim = false;
$inbox = false;
$commands = $this->build_list_commands($lsub, $mailbox, $keyword);
$commands = $this->build_list_commands($lsub, $mailbox, $keyword, $children_capability);

$cache_command = implode('', array_map(function($v) { return $v[0]; }, $commands)).(string)$mailbox.(string)$keyword;
$cache = $this->check_cache($cache_command);
if ($cache !== false) {
Expand All @@ -547,6 +548,11 @@ public function get_mailbox_list($lsub=false, $mailbox='', $keyword='*') {
$this->send_command($command);
$result = $this->get_response($this->folder_max, true);

if (!$children_capability) {
$delim = $result[0][count($result[0]) - 2];
$result = $this->preprocess_folders($result, $mailbox, $delim);
}

/* loop through the "parsed" response. Each iteration is one folder */
foreach ($result as $vals) {

Expand Down Expand Up @@ -702,6 +708,68 @@ public function get_mailbox_list($lsub=false, $mailbox='', $keyword='*') {
return $this->cache_return_val($folders, $cache_command);
}

/**
* Preprocess the folder list to determine if a folder has children
* @param array $result the folder list
* @param string $mailbox the mailbox to limit the results to
* @param string $delim the folder delimiter
* @return array the processed folder list
*/
function preprocess_folders($result, $mailbox, $delim) {
$folderPaths = [];
$processedResult = [];

// Step 1: Extract all folder paths from the array (using the last element in each sub-array)
foreach ($result as $entry) {
if (isset($entry[count($entry) - 1]) && is_string($entry[count($entry) - 1])) {
$folderPaths[] = $entry[count($entry) - 1];
}
}

// Step 2: Process each folder to determine if it has subfolders
foreach ($result as $entry) {
if (isset($entry[count($entry) - 1]) && is_string($entry[count($entry) - 1])) {
$currentFolder = $entry[count($entry) - 1];
$hasChildren = false;

// Check if any other folder starts with the current folder followed by the delimiter
foreach ($folderPaths as $path) {
if (strpos($path, $currentFolder . $delim) === 0) {
$hasChildren = true;
break;
}
}

// Add the appropriate flag (\HasChildren or \HasNoChildren)
$entry = array_merge(
array_slice($entry, 0, 3),
[$hasChildren ? "\\HasChildren" : "\\HasNoChildren"],
array_slice($entry, 3)
);

// Root folder processing
if (empty($mailbox)) {
if (strpos($currentFolder, $delim) === false) {
$processedResult[] = $entry;
}
} else {
// Process subfolders of the given mailbox
$expectedPrefix = $mailbox . $delim;
if (strpos($currentFolder, $expectedPrefix) === 0) {
$remainingPath = substr($currentFolder, strlen($expectedPrefix));
// Include only direct subfolders (no further delimiters in the remaining path)
if (strpos($remainingPath, $delim) === false) {
$processedResult[] = $entry;
}
}
}
} else {
$processedResult[] = $entry;
}
}
return $processedResult;
}

/**
* Sort a folder list with the inbox at the top
*/
Expand Down Expand Up @@ -2411,7 +2479,13 @@ public function get_mailbox_page($mailbox, $sort, $rev, $filter, $offset=0, $lim
*/
public function get_folder_list_by_level($level='', $only_subscribed=false, $with_input = false) {
$result = array();
$folders = $this->get_mailbox_list($only_subscribed, $level, '%');
$folders = array();
if ($this->server_support_children_capability()) {
$folders = $this->get_mailbox_list($only_subscribed, $level, '%');
} else {
$folders = $this->get_mailbox_list($only_subscribed, $level, "*", false);
}

foreach ($folders as $name => $folder) {
$result[$name] = array(
'name' => $folder['name'],
Expand All @@ -2428,7 +2502,7 @@ public function get_folder_list_by_level($level='', $only_subscribed=false, $wit
}
}
if ($only_subscribed || $with_input) {
$subscribed_folders = array_column($this->get_mailbox_list(true), 'name');
$subscribed_folders = array_column($this->get_mailbox_list(true, children_capability:$this->server_support_children_capability()), 'name');
foreach ($result as $key => $folder) {
$result[$key]['subscribed'] = in_array($folder['name'], $subscribed_folders);
if (!$with_input) {
Expand Down Expand Up @@ -2486,5 +2560,38 @@ protected function server_supports_custom_headers() {

return true;
}

protected function server_support_children_capability() {
$test_command = 'CAPABILITY'."\r\n";
$this->send_command($test_command);
$response = $this->get_response(false, true);
$status = $this->check_response($response, true);

// Keywords that indicate the header search is not supported
$keywords = ['CHILDREN'];

if (!$status) {
return false;
}

// Flatten the response array to a single array of strings
$flattened_response = array_reduce($response, 'array_merge', []);

// Check if all keywords are present in the flattened response
$sequence_match = true;
foreach ($keywords as $keyword) {
if (!in_array($keyword, $flattened_response)) {
$sequence_match = false;
break;
}
}

// If all keywords are found, the header search is not supported
if ($sequence_match) {
return true;
}

return false;
}
}
}

0 comments on commit 8447c85

Please sign in to comment.