From 66c871550258ad7d7f5be392174387b89120bef2 Mon Sep 17 00:00:00 2001 From: Victor Emanouilov Date: Wed, 9 Oct 2024 16:17:39 +0300 Subject: [PATCH 1/7] add mailbox bridge to imap module, subclass for imap/jmap and ews support, add ews package --- composer.json | 3 +- composer.lock | 356 ++++++++++++++++++- modules/imap/functions.php | 55 ++- modules/imap/handler_modules.php | 565 +++++++++++-------------------- modules/imap/hm-ews.php | 19 ++ modules/imap/hm-imap.php | 17 +- modules/imap/hm-mailbox.php | 339 +++++++++++++++++++ 7 files changed, 942 insertions(+), 412 deletions(-) create mode 100644 modules/imap/hm-ews.php create mode 100644 modules/imap/hm-mailbox.php diff --git a/composer.json b/composer.json index 6e4993990d..4cb7b48dcf 100644 --- a/composer.json +++ b/composer.json @@ -55,7 +55,8 @@ "twbs/bootstrap": "^5.3", "twbs/bootstrap-icons": "^1.11", "webklex/composer-info": "^0.0.1", - "zbateson/mail-mime-parser": "^2.4" + "zbateson/mail-mime-parser": "^2.4", + "garethp/php-ews": "^0.10.1" }, "require-dev": { "phpunit/phpunit": "^10.5" diff --git a/composer.lock b/composer.lock index b7fd518d53..909e66ac8d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8de6d181041346fe7a32bd3994050781", + "content-hash": "ad61b5e9e80d518e0ffbd8c67609fa7a", "packages": [ { "name": "bacon/bacon-qr-code", @@ -305,6 +305,306 @@ }, "time": "2023-11-17T15:01:25+00:00" }, + { + "name": "garethp/http-playback", + "version": "v2.0", + "source": { + "type": "git", + "url": "https://github.com/Garethp/HttpPlayback.git", + "reference": "50fd7b75c3d08ac0548df6d834a60ac65fc365df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Garethp/HttpPlayback/zipball/50fd7b75c3d08ac0548df6d834a60ac65fc365df", + "reference": "50fd7b75c3d08ac0548df6d834a60ac65fc365df", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "~7.0" + }, + "require-dev": { + "phpunit/phpunit": "~9.5", + "squizlabs/php_codesniffer": "~3.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "garethp\\HttpPlayback\\": "src/", + "garethp\\HttpPlayback\\Test\\": "tests/src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "support": { + "issues": "https://github.com/Garethp/HttpPlayback/issues", + "source": "https://github.com/Garethp/HttpPlayback/tree/v2.0" + }, + "time": "2021-05-10T01:13:55+00:00" + }, + { + "name": "garethp/php-ews", + "version": "v0.10.1", + "source": { + "type": "git", + "url": "https://github.com/Garethp/php-ews.git", + "reference": "6fbc1b17d0c04fe313c40c5d712090638f6a1c84" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Garethp/php-ews/zipball/6fbc1b17d0c04fe313c40c5d712090638f6a1c84", + "reference": "6fbc1b17d0c04fe313c40c5d712090638f6a1c84", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-dom": "*", + "ext-libxml": "*", + "ext-simplexml": "*", + "ext-soap": "*", + "garethp/http-playback": "^2.0" + }, + "require-dev": { + "goetas/xsd-reader": "^2.0-dev", + "goetas/xsd2php": "^2.1", + "mockery/mockery": "^1.4", + "phpunit/phpunit": "~9.5", + "squizlabs/php_codesniffer": "~3.6.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Utilities/ensureIsArray.php", + "src/Utilities/ensureIsDateTime.php", + "src/Utilities/cloneValue.php", + "src/Utilities/getFolderIds.php" + ], + "psr-4": { + "garethp\\ews\\": "src/", + "garethp\\ews\\Test\\": "tests/src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "A PHP Library to interact with the Exchange SOAP service", + "support": { + "issues": "https://github.com/Garethp/php-ews/issues", + "source": "https://github.com/Garethp/php-ews/tree/v0.10.1" + }, + "time": "2021-09-17T15:21:51+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.1", + "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:35:24+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", + "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2024-07-18T10:29:17+00:00" + }, { "name": "guzzlehttp/psr7", "version": "2.6.2", @@ -1036,6 +1336,58 @@ }, "time": "2019-01-08T18:20:26+00:00" }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, { "name": "psr/http-factory", "version": "1.0.2", @@ -3605,5 +3957,5 @@ "platform-overrides": { "php": "8.1" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/modules/imap/functions.php b/modules/imap/functions.php index 107f95daec..ed2b80d00a 100644 --- a/modules/imap/functions.php +++ b/modules/imap/functions.php @@ -1282,14 +1282,11 @@ function get_request_params($request) { }} if (!hm_exists('snooze_message')) { -function snooze_message($imap, $msg_id, $folder, $snooze_tag) { - if (!$imap->select_mailbox($folder)) { - return false; - } +function snooze_message($mailbox, $msg_id, $folder, $snooze_tag) { if (!$snooze_tag) { - $imap->message_action('UNREAD', array($msg_id)); + $mailbox->message_action($folder, 'UNREAD', array($msg_id)); } - $msg = $imap->get_message_content($msg_id, 0); + $msg = $mailbox->get_message_content($folder, $msg_id); preg_match("/^X-Snoozed:.*(\r?\n[ \t]+.*)*\r?\n?/im", $msg, $matches); if (count($matches)) { $msg = str_replace($matches[0], '', $msg); @@ -1306,39 +1303,30 @@ function snooze_message($imap, $msg_id, $folder, $snooze_tag) { $res = false; $snooze_folder = 'Snoozed'; if ($snooze_tag) { - if (!count($imap->get_mailbox_status($snooze_folder))) { - $imap->create_mailbox($snooze_folder); - } - if ($imap->select_mailbox($snooze_folder) && $imap->append_start($snooze_folder, mb_strlen($msg))) { - $imap->append_feed($msg."\r\n"); - if ($imap->append_end()) { - if ($imap->select_mailbox($folder) && $imap->message_action('DELETE', array($msg_id))) { - $imap->message_action('EXPUNGE', array($msg_id)); - $res = true; - } + if (!count($mailbox->get_folder_status($snooze_folder))) { + $mailbox->create_folder($snooze_folder); + } + if ($mailbox->store_message($snooze_folder, $msg)) { + if ($mailbox->message_action($folder, 'DELETE', array($msg_id))) { + $mailbox->message_action($folder, 'EXPUNGE', array($msg_id)); + $res = true; } } } else { $snooze_headers = parse_snooze_header($matches[0]); $original_folder = $snooze_headers['from']; - if ($imap->select_mailbox($original_folder) && $imap->append_start($original_folder, mb_strlen($msg))) { - $imap->append_feed($msg."\r\n"); - if ($imap->append_end()) { - if ($imap->select_mailbox($snooze_folder) && $imap->message_action('DELETE', array($msg_id))) { - $imap->message_action('EXPUNGE', array($msg_id)); - $res = true; - } + if ($mailbox->store_message($original_folder, $msg)) { + if ($mailbox->message_action($snooze_folder, 'DELETE', array($msg_id))) { + $mailbox->message_action($snooze_folder, 'EXPUNGE', array($msg_id)); + $res = true; } } } return $res; }} if (!hm_exists('add_tag_to_message')) { -function add_tag_to_message($imap, $msg_id, $folder, $tag) { - if (!$imap->select_mailbox($folder)) { - return false; - } - $msg = $imap->get_message_content($msg_id, 0); +function add_tag_to_message($mailbox, $msg_id, $folder, $tag) { + $msg = $mailbox->get_message_content($folder, $msg_id); preg_match("/^X-Cypht-Tags:(.+)\r?\n/i", $msg, $matches); if (count($matches)) { @@ -1359,13 +1347,10 @@ function add_tag_to_message($imap, $msg_id, $folder, $tag) { $msg = rtrim($msg)."\r\n"; $res = false; - if ($imap->append_start($folder, strlen($msg))) { - $imap->append_feed($msg."\r\n"); - if ($imap->append_end()) { - if ($imap->message_action('DELETE', array($msg_id))) { - $imap->message_action('EXPUNGE', array($msg_id)); - $res = true; - } + if ($mailbox->store_message($folder, $msg)) { + if ($mailbox->message_action($folder, 'DELETE', array($msg_id))) { + $mailbox->message_action($folder, 'EXPUNGE', array($msg_id)); + $res = true; } } diff --git a/modules/imap/handler_modules.php b/modules/imap/handler_modules.php index 9008f4e99a..5eb4ca8cc1 100644 --- a/modules/imap/handler_modules.php +++ b/modules/imap/handler_modules.php @@ -37,15 +37,11 @@ public function process() { if (!$filepath) { return; } - $cache = Hm_IMAP_List::get_cache($this->cache, $path[1]); - $imap = Hm_IMAP_List::connect($path[1], $cache); - if (!imap_authed($imap)) { + $mailbox = Hm_IMAP_List::get_connected_mailbox($path[1], $this->cache); + if (! $mailbox) { return; } - if (!$imap->select_mailbox(hex2bin($path[2]))) { - return; - } - $content = $imap->get_message_content($uid, 0); + $content = $mailbox->get_message_content(hex2bin($path[2]), $uid); if (!$content) { return; } @@ -71,10 +67,9 @@ class Hm_Handler_imap_folder_status extends Hm_Handler_Module { public function process() { list($success, $form) = $this->process_form(array('imap_server_id', 'folder')); if ($success) { - $cache = Hm_IMAP_List::get_cache($this->cache, $form['imap_server_id']); - $imap = Hm_IMAP_List::connect($form['imap_server_id'], $cache); - if (imap_authed($imap)) { - $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $imap->get_mailbox_status(hex2bin($form['folder'])))); + $mailbox = Hm_IMAP_List::get_connected_mailbox($form['imap_server_id'], $this->cache); + if ($mailbox && $mailbox->authed()) { + $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $mailbox->get_folder_status(hex2bin($form['folder'])))); } } } @@ -255,16 +250,17 @@ public function process() { $screen = false; $parts = explode("_", $this->request->get['list_path']); $imap_server_id = $parts[1] ?? ''; - $cache = Hm_IMAP_List::get_cache($this->cache, $imap_server_id); - $imap = Hm_IMAP_List::connect($imap_server_id, $cache); if ($form['imap_move_action'] == "screen_mail") { - $form['imap_move_action'] = "move"; - $screen = true; - $screen_folder = 'Screen emails'; - if (!count($imap->get_mailbox_status($screen_folder))) { - $imap->create_mailbox($screen_folder); + $mailbox = Hm_IMAP_List::get_connected_mailbox($imap_server_id, $this->cache); + if ($mailbox && $mailbox->authed()) { + $form['imap_move_action'] = "move"; + $screen = true; + $screen_folder = 'Screen emails'; + if (! count($mailbox->get_folder_status($screen_folder))) { + $mailbox->create_folder($screen_folder); + } + $form['imap_move_to'] = $parts[0] ."_". $parts[1] ."_".bin2hex($screen_folder); } - $form['imap_move_to'] = $parts[0] ."_". $parts[1] ."_".bin2hex($screen_folder); } list($msg_ids, $dest_path, $same_server_ids, $other_server_ids) = process_move_to_arguments($form); @@ -330,18 +326,17 @@ public function process() { $msg = str_replace("\r\n", "\n", $msg); $msg = str_replace("\n", "\r\n", $msg); $msg = rtrim($msg)."\r\n"; - $cache = Hm_IMAP_List::get_cache($this->cache, $imap_id); - $imap = Hm_IMAP_List::connect($imap_id, $cache); $imap_details = Hm_IMAP_List::dump($imap_id); $sent_folder = false; - if (imap_authed($imap)) { + $mailbox = Hm_IMAP_List::get_connected_mailbox($imap_id, $this->cache); + if ($mailbox && $mailbox->authed()) { $specials = get_special_folders($this, $imap_id); if (array_key_exists('sent', $specials) && $specials['sent']) { $sent_folder = $specials['sent']; } if (!$sent_folder) { - $auto_sent = $imap->get_special_use_mailboxes('sent'); + $auto_sent = $mailbox->get_special_use_mailboxes('sent'); if (!array_key_exists('sent', $auto_sent)) { return; } @@ -352,16 +347,13 @@ public function process() { } if ($sent_folder) { Hm_Debug::add(sprintf("Attempting to save sent message for IMAP server %s in folder %s", $imap_details['server'], $sent_folder)); - if ($imap->append_start($sent_folder, mb_strlen($msg), true)) { - $imap->append_feed($msg."\r\n"); - if (!$imap->append_end()) { - Hm_Msgs::add('ERRAn error occurred saving the sent message'); - } + if (! $mailbox->store_message($sent_folder, $msg)) { + Hm_Msgs::add('ERRAn error occurred saving the sent message'); } $uid = null; - $mailbox_page = $imap->get_mailbox_page($sent_folder, 'ARRIVAL', true, 'ALL', 0, 10); + $mailbox_page = $mailbox->get_messages($sent_folder, 'ARRIVAL', true, 'ALL', 0, 10); foreach ($mailbox_page[1] as $mail) { - $msg_header = $imap->get_message_headers($mail['uid']); + $msg_header = $mailbox->get_message_headers($mail['uid']); if ($msg_header['Message-Id'] === $mime->get_headers()['Message-Id']) { $uid = $mail['uid']; break; @@ -386,10 +378,9 @@ public function process() { if ($success) { $path = explode('_', $form['compose_msg_path']); if (count($path) == 3 && $path[0] == 'imap') { - $cache = Hm_IMAP_List::get_cache($this->cache, $path[1]); - $imap = Hm_IMAP_List::connect($path[1], $cache); - if (imap_authed($imap) && $imap->select_mailbox(hex2bin($path[2]))) { - $imap->message_action('UNFLAG', array($form['compose_msg_uid'])); + $mailbox = Hm_IMAP_List::get_connected_mailbox($path[1], $this->cache); + if ($mailbox && $mailbox->authed()) { + $mailbox->message_action(hex2bin($path[2]), 'UNFLAG', array($form['compose_msg_uid'])); } } } @@ -408,11 +399,10 @@ public function process() { if ($success) { $path = explode('_', $form['compose_msg_path']); if (count($path) == 3 && $path[0] == 'imap') { - $cache = Hm_IMAP_List::get_cache($this->cache, $path[1]); - $imap = Hm_IMAP_List::connect($path[1], $cache); - if (imap_authed($imap) && $imap->select_mailbox(hex2bin($path[2]))) { - $this->out('folder_status', array('imap_'.$path[1].'_'.$path[2] => $imap->folder_state)); - $imap->message_action('ANSWERED', array($form['compose_msg_uid'])); + $mailbox = Hm_IMAP_List::get_connected_mailbox($path[1], $this->cache); + if ($mailbox && $mailbox->authed()) { + $this->out('folder_status', array('imap_'.$path[1].'_'.$path[2] => $mailbox->getFolderState())); + $mailbox->message_action(hex2bin($path[2]), 'ANSWERED', array($form['compose_msg_uid'])); } } } @@ -431,11 +421,10 @@ class Hm_Handler_imap_mark_as_read extends Hm_Handler_Module { public function process() { list($success, $form) = $this->process_form(array('imap_server_id', 'imap_msg_uid', 'folder')); if ($success) { - $cache = Hm_IMAP_List::get_cache($this->cache, $form['imap_server_id']); - $imap = Hm_IMAP_List::connect($form['imap_server_id'], $cache); - if (imap_authed($imap) && $imap->select_mailbox(hex2bin($form['folder']))) { - $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $imap->folder_state)); - $imap->message_action('READ', array($form['imap_msg_uid'])); + $mailbox = Hm_IMAP_List::get_connected_mailbox($form['imap_server_id'], $this->cache); + if ($mailbox && $mailbox->authed()) { + $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $mailbox->getFolderState())); + $mailbox->message_action(hex2bin($form['folder']), 'READ', array($form['imap_msg_uid'])); } } } @@ -497,49 +486,14 @@ public function process() { $msg_id = preg_replace("/^0.{1}/", '', $this->request->get['imap_msg_part']); } if ($server_id !== NULL && $uid !== NULL && $folder !== NULL && $msg_id !== NULL) { - $cache = Hm_IMAP_List::get_cache($this->cache, $server_id); - $imap = Hm_IMAP_List::connect($server_id, $cache); - if (imap_authed($imap)) { - if ($imap->select_mailbox($folder)) { - $msg_struct = $imap->get_message_structure($uid); - $struct = $imap->search_bodystructure($msg_struct, array('imap_part_number' => $msg_id)); - if (!empty($struct)) { - $part_struct = array_shift($struct); - $encoding = false; - if (array_key_exists('encoding', $part_struct)) { - $encoding = trim(mb_strtolower($part_struct['encoding'])); - } - $stream_size = $imap->start_message_stream($uid, $msg_id); - if ($stream_size > 0) { - $charset = ''; - if (array_key_exists('attributes', $part_struct)) { - if (is_array($part_struct['attributes']) && array_key_exists('charset', $part_struct['attributes'])) { - $charset = '; charset='.$part_struct['attributes']['charset']; - } - } - header('Content-Type: '.$part_struct['type'].'/'.$part_struct['subtype'].$charset); - header('Content-Transfer-Encoding: binary'); - ob_end_clean(); - $output_line = ''; - while($line = $imap->read_stream_line()) { - if ($encoding == 'quoted-printable') { - $line = quoted_printable_decode($line); - } - elseif ($encoding == 'base64') { - $line = base64_decode($line); - } - echo $output_line; - $output_line = $line; - - } - if ($part_struct['type'] == 'text') { - $output_line = preg_replace("/\)(\r\n)$/m", '$1', $output_line); - } - echo $output_line; - Hm_Functions::cease(); - } - } - } + $mailbox = Hm_IMAP_List::get_connected_mailbox($server_id, $this->cache); + if ($mailbox && $mailbox->authed()) { + $mailbox->stream_message_part($folder, $uid, $msg_id, function ($content_type) { + header('Content-Type: ' . $content_type); + header('Content-Transfer-Encoding: binary'); + ob_end_clean(); + }); + Hm_Functions::cease(); } } Hm_Msgs::add('ERRAn Error occurred trying to download the message'); @@ -560,50 +514,15 @@ public function process() { list($server_id, $uid, $folder, $msg_id) = get_request_params($this->request->get); if ($server_id !== NULL && $uid !== NULL && $folder !== NULL && $msg_id !== NULL) { - $cache = Hm_IMAP_List::get_cache($this->cache, $server_id); - $imap = Hm_IMAP_List::connect($server_id, $cache); - if (imap_authed($imap)) { - if ($imap->select_mailbox($folder)) { - $msg_struct = $imap->get_message_structure($uid); - $struct = $imap->search_bodystructure($msg_struct, array('imap_part_number' => $msg_id)); - if (!empty($struct)) { - $part_struct = array_shift($struct); - $encoding = false; - if (array_key_exists('encoding', $part_struct)) { - $encoding = trim(mb_strtolower($part_struct['encoding'])); - } - $stream_size = $imap->start_message_stream($uid, $msg_id); - if ($stream_size > 0) { - $name = get_imap_part_name($part_struct, $uid, $msg_id); - header('Content-Disposition: attachment; filename="'.$name.'"'); - $charset = ''; - if (array_key_exists('attributes', $part_struct)) { - if (is_array($part_struct['attributes']) && array_key_exists('charset', $part_struct['attributes'])) { - $charset = '; charset='.$part_struct['attributes']['charset']; - } - } - header('Content-Type: '.$part_struct['type'].'/'.$part_struct['subtype'].$charset); - header('Content-Transfer-Encoding: binary'); - ob_end_clean(); - $output_line = ''; - while($line = $imap->read_stream_line()) { - if ($encoding == 'quoted-printable') { - $line = quoted_printable_decode($line); - } - elseif ($encoding == 'base64') { - $line = base64_decode($line); - } - echo $output_line; - $output_line = $line; - } - if ($part_struct['type'] == 'text') { - $output_line = preg_replace("/\)(\r\n)$/m", '$1', $output_line); - } - echo $output_line; - Hm_Functions::cease(); - } - } - } + $mailbox = Hm_IMAP_List::get_connected_mailbox($server_id, $this->cache); + if ($mailbox && $mailbox->authed()) { + $mailbox->stream_message_part($folder, $uid, $msg_id, function ($content_type, $part_name) { + header('Content-Disposition: attachment; filename="' . $part_name . '"'); + header('Content-Type: ' . $content_type); + header('Content-Transfer-Encoding: binary'); + ob_end_clean(); + }); + Hm_Functions::cease(); } } Hm_Msgs::add('ERRAn Error occurred trying to download the message'); @@ -698,29 +617,12 @@ public function process() { if (array_key_exists('imap_remove_attachment', $this->request->get) && $this->request->get['imap_remove_attachment']) { list($server_id, $uid, $folder, $msg_id) = get_request_params($this->request->get); if ($server_id !== NULL && $uid !== NULL && $folder !== NULL && $msg_id !== NULL) { - $cache = Hm_IMAP_List::get_cache($this->cache, $server_id); - $imap = Hm_IMAP_List::connect($server_id, $cache); - if (imap_authed($imap)) { - if ($imap->select_mailbox($folder)) { - $msg = $imap->get_message_content($uid, 0, false, false); - if ($msg) { - $attachment_id = get_attachment_id_for_mail_parser($imap, $uid, $this->request->get['imap_msg_part']); - if ($attachment_id !== false) { - $msg = remove_attachment($attachment_id, $msg); - if ($imap->append_start($folder, mb_strlen($msg))) { - $imap->append_feed($msg."\r\n"); - if ($imap->append_end()) { - if ($imap->message_action('DELETE', array($uid))) { - $imap->message_action('EXPUNGE', array($uid)); - Hm_Msgs::add('Attachment deleted'); - $this->out('redirect_url', '?page=message_list&list_path='.$this->request->get['list_path']); - return; - } - } - } - } - - } + $mailbox = Hm_IMAP_List::get_connected_mailbox($server_id, $this->cache); + if ($mailbox && $mailbox->authed()) { + if ($mailbox->remove_attachment($folder, $uid, $this->request->get['imap_msg_part'])) { + Hm_Msgs::add('Attachment deleted'); + $this->out('redirect_url', '?page=message_list&list_path=' . $this->request->get['list_path']); + return; } } } @@ -753,12 +655,11 @@ public function process() { $this->session->set('imap_prefetched_ids', array_unique($prefetched, SORT_STRING)); } $with_subscription = isset($this->request->post['subscription_state']) && $this->request->post['subscription_state']; - $cache = Hm_IMAP_List::get_cache($this->cache, $form['imap_server_id']); - $imap = Hm_IMAP_List::connect($form['imap_server_id'], $cache); - if (imap_authed($imap)) { - $quota_root = $imap->get_quota_root($folder ? $folder : 'INBOX'); + $mailbox = Hm_IMAP_List::get_connected_mailbox($form['imap_server_id'], $this->cache); + if ($mailbox && $mailbox->authed()) { + $quota_root = $mailbox->get_quota($folder ? $folder : 'INBOX', true); if ($quota_root && isset($quota_root[0]['name'])) { - $quota = $imap->get_quota($quota_root[0]['name']); + $quota = $mailbox->get_quota($quota_root[0]['name'], false); if ($quota) { $current = floatval($quota[0]['current']); $max = floatval($quota[0]['max']); @@ -833,9 +734,8 @@ public function process() { } $path = sprintf("imap_%s_%s", $form['imap_server_id'], $form['folder']); $details = Hm_IMAP_List::dump($form['imap_server_id']); - $cache = Hm_IMAP_List::get_cache($this->cache, $form['imap_server_id']); - $imap = Hm_IMAP_List::connect($form['imap_server_id'], $cache); - if (imap_authed($imap)) { + $mailbox = Hm_IMAP_List::get_connected_mailbox($form['imap_server_id'], $this->cache); + if ($mailbox && $mailbox->authed()) { $this->out('imap_mailbox_page_path', $path); if (isset($this->request->get['screen_emails']) && hex2bin($form['folder']) == 'INBOX' && $this->module_is_supported("contacts")) { $contacts = $this->get('contact_store'); @@ -844,9 +744,9 @@ public function process() { $existingEmails = array_map(function($c){ return $c->value('email_address'); },$contact_list); - list($total, $results) = $imap->get_mailbox_page(hex2bin($form['folder']), $sort, $rev, $filter, $offset, $limit, $keyword, $existingEmails); + list($total, $results) = $mailbox->get_messages(hex2bin($form['folder']), $sort, $rev, $filter, $offset, $limit, $keyword, $existingEmails); } else { - list($total, $results) = $imap->get_mailbox_page(hex2bin($form['folder']), $sort, $rev, $filter, $offset, $limit, $keyword); + list($total, $results) = $mailbox->get_messages(hex2bin($form['folder']), $sort, $rev, $filter, $offset, $limit, $keyword); } foreach ($results as $msg) { $msg['server_id'] = $form['imap_server_id']; @@ -854,11 +754,11 @@ public function process() { $msg['folder'] = $form['folder']; $msgs[] = $msg; } - if ($imap->selected_mailbox) { - $imap->selected_mailbox['detail']['exists'] = $total; - $this->out('imap_folder_detail', array_merge($imap->selected_mailbox, array('offset' => $offset, 'limit' => $limit))); + if ($folder = $mailbox->get_selected_folder()) { + $folder['detail']['exists'] = $total; + $this->out('imap_folder_detail', array_merge($folder, array('offset' => $offset, 'limit' => $limit))); } - $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $imap->folder_state)); + $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $mailbox->get_folder_status())); } $this->out('imap_mailbox_page', $msgs); $this->out('list_page', $list_page); @@ -899,28 +799,17 @@ public function process() { list($success, $form) = $this->process_form(array('imap_msg_uid', 'imap_server_id', 'folder')); if ($success) { $del_result = false; - $cache = Hm_IMAP_List::get_cache($this->cache, $form['imap_server_id']); - $imap = Hm_IMAP_List::connect($form['imap_server_id'], $cache); $trash_folder = false; $specials = get_special_folders($this, $form['imap_server_id']); if (array_key_exists('trash', $specials) && $specials['trash']) { $trash_folder = $specials['trash']; } - if (imap_authed($imap)) { - if ($imap->select_mailbox(hex2bin($form['folder']))) { - $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $imap->folder_state)); - if ($trash_folder && $trash_folder != hex2bin($form['folder'])) { - if ($imap->message_action('MOVE', array($form['imap_msg_uid']), $trash_folder)) { - $del_result = true; - } - } - else { - if ($imap->message_action('DELETE', array($form['imap_msg_uid']))) { - $del_result = true; - $imap->message_action('EXPUNGE', array($form['imap_msg_uid'])); - } - } + $mailbox = Hm_IMAP_List::get_connected_mailbox($form['imap_server_id'], $this->cache); + if ($mailbox && $mailbox->authed()) { + if ($mailbox->delete_message(hex2bin($form['folder']), $form['imap_msg_uid'], $trash_folder)) { + $del_result = true; } + $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $mailbox->get_folder_status())); } if (!$del_result) { Hm_Msgs::add('ERRAn error occurred trying to delete this message'); @@ -950,8 +839,7 @@ public function process() { if (!$success) { return; } - $cache = Hm_IMAP_List::get_cache($this->cache, $form['imap_server_id']); - $imap = Hm_IMAP_List::connect($form['imap_server_id'], $cache); + $archive_folder = false; $errors = 0; @@ -964,8 +852,9 @@ public function process() { $errors++; } - if (!$errors && imap_authed($imap)) { - $archive_exists = count($imap->get_mailbox_status($archive_folder)); + $mailbox = Hm_IMAP_List::get_connected_mailbox($form['imap_server_id'], $this->cache); + if (! $errors && $mailbox && $mailbox->authed()) { + $archive_exists = count($mailbox->get_folder_status($archive_folder)); if (!$archive_exists) { Hm_Msgs::add('Configured archive folder for this IMAP server does not exist'); $errors++; @@ -973,18 +862,12 @@ public function process() { $form_folder = hex2bin($form['folder']); - /* select source folder */ - if ($errors || !$imap->select_mailbox($form_folder)) { - Hm_Msgs::add('ERRAn error occurred archiving the message'); - $errors++; - } - /* path according to original option setting */ if ($this->user_config->get('original_folder_setting', false)) { - $archive_folder .= '/'.$form_folder; - if (!count($imap->get_mailbox_status($archive_folder))) { - if (! $imap->create_mailbox($archive_folder)) { - $debug = $imap->show_debug(true, true, true); + $archive_folder .= '/' . $form_folder; + if (!count($mailbox->get_folder_status($archive_folder))) { + if (! $mailbox->create_folder($archive_folder)) { + $debug = $mailbox->get_debug(); if (! empty($debug['debug'])) { Hm_Msgs::add('ERR' . array_pop($debug['debug'])); } else { @@ -996,7 +879,7 @@ public function process() { } /* try to move the message */ - if (!$errors && $imap->message_action('MOVE', array($form['imap_msg_uid']), $archive_folder)) { + if (! $errors && $mailbox->message_action($form_folder, 'MOVE', array($form['imap_msg_uid']), $archive_folder)) { Hm_Msgs::add("Message archived"); } else { @@ -1019,21 +902,18 @@ public function process() { list($success, $form) = $this->process_form(array('imap_flag_state', 'imap_msg_uid', 'imap_server_id', 'folder')); if ($success) { $flag_result = false; - $cache = Hm_IMAP_List::get_cache($this->cache, $form['imap_server_id']); - $imap = Hm_IMAP_List::connect($form['imap_server_id'], $cache); - if (imap_authed($imap)) { - if ($imap->select_mailbox(hex2bin($form['folder']))) { - $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $imap->folder_state)); - if ($form['imap_flag_state'] == 'flagged') { - $cmd = 'UNFLAG'; - } - else { - $cmd = 'FLAG'; - } - if ($imap->message_action($cmd, array($form['imap_msg_uid']))) { - $flag_result = true; - } + $mailbox = Hm_IMAP_List::get_connected_mailbox($form['imap_server_id'], $this->cache); + if ($mailbox && $mailbox->authed()) { + if ($form['imap_flag_state'] == 'flagged') { + $cmd = 'UNFLAG'; + } + else { + $cmd = 'FLAG'; + } + if ($mailbox->message_action(hex2bin($form['folder']), $cmd, array($form['imap_msg_uid']))) { + $flag_result = true; } + $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $mailbox->get_folder_status())); } if (!$flag_result) { Hm_Msgs::add('ERRAn error occurred trying to flag this message'); @@ -1065,11 +945,10 @@ public function process() { $ids = explode(',', $form['imap_snooze_ids']); foreach ($ids as $msg_part) { list($imap_server_id, $msg_id, $folder) = explode('_', $msg_part); - $cache = Hm_IMAP_List::get_cache($this->cache, $imap_server_id); - $imap = Hm_IMAP_List::connect($imap_server_id, $cache); - if (imap_authed($imap)) { + $mailbox = Hm_IMAP_List::get_connected_mailbox($imap_server_id, $this->cache); + if ($mailbox && $mailbox->authed()) { $folder = hex2bin($folder); - if (snooze_message($imap, $msg_id, $folder, $snooze_tag)) { + if (snooze_message($mailbox, $msg_id, $folder, $snooze_tag)) { $snoozed_messages++; } } @@ -1104,11 +983,10 @@ public function process() { $ids = explode(',', $form['imap_server_ids']); foreach ($ids as $msg_part) { list($imap_server_id, $msg_id, $folder) = explode('_', $msg_part); - $cache = Hm_IMAP_List::get_cache($this->cache, $imap_server_id); - $imap = Hm_IMAP_List::connect($imap_server_id, $cache); - if (imap_authed($imap)) { + $mailbox = Hm_IMAP_List::get_connected_mailbox($imap_server_id, $this->cache); + if ($mailbox && $mailbox->authed()) { $folder = hex2bin($folder); - if (add_tag_to_message($imap, $msg_id, $folder, $form['tag_id'])) { + if (add_tag_to_message($mailbox, $msg_id, $folder, $form['tag_id'])) { $taged_messages++; } } @@ -1137,21 +1015,20 @@ class Hm_Handler_imap_unsnooze_message extends Hm_Handler_Module { public function process() { $servers = Hm_IMAP_List::dump(); foreach (array_keys($servers) as $server_id) { - $cache = Hm_IMAP_List::get_cache($this->cache, $server_id); - $imap = Hm_IMAP_List::connect($server_id, $cache); - if (imap_authed($imap)) { + $mailbox = Hm_IMAP_List::get_connected_mailbox($server_id, $this->cache); + if ($mailbox && $mailbox->authed()) { $folder = 'Snoozed'; - if (!count($imap->get_mailbox_status($folder))) { + if (! count($mailbox->get_folder_status($folder))) { continue; } - $ret = $imap->get_mailbox_page($folder, 'DATE', false, 'ALL'); + $ret = $mailbox->get_messages($folder, 'DATE', false, 'ALL'); foreach ($ret[1] as $msg) { - $msg_headers = $imap->get_message_headers($msg['uid']); + $msg_headers = $mailbox->get_message_headers($msg['uid']); if (isset($msg_headers['X-Snoozed'])) { try { $snooze_headers = parse_snooze_header($msg_headers['X-Snoozed']); if (new DateTime($snooze_headers['until']) <= new DateTime()) { - snooze_message($imap, $msg['uid'], $folder, null); + snooze_message($mailbox, $msg['uid'], $folder, null); } } catch (Exception $e) { Hm_Debug::add(sprintf('ERR Cannot unsnooze message: %s', $msg_headers['subject'])); @@ -1184,9 +1061,8 @@ public function process() { $specials = get_special_folders($this, $server); $trash_folder = false; $archive_folder = false; - $cache = Hm_IMAP_List::get_cache($this->cache, $server); - $imap = Hm_IMAP_List::connect($server, $cache); - if (imap_authed($imap)) { + $mailbox = Hm_IMAP_List::get_connected_mailbox($server, $this->cache); + if ($mailbox && $mailbox->authed()) { $server_details = $this->user_config->get('imap_servers')[$server]; if ($form['action_type'] == 'delete') { if (array_key_exists('trash', $specials)) { @@ -1208,46 +1084,44 @@ public function process() { } foreach ($folders as $folder => $uids) { - if ($imap->select_mailbox(hex2bin($folder))) { - $status['imap_'.$server.'_'.$folder] = $imap->folder_state; + $status['imap_'.$server.'_'.$folder] = $imap->folder_state; - if ($form['action_type'] == 'delete' && $trash_folder && $trash_folder != hex2bin($folder)) { - if (!$imap->message_action('MOVE', $uids, $trash_folder)) { - $errs++; - } - else { - foreach ($uids as $uid) { - $moved[] = sprintf("imap_%s_%s_%s", $server, $uid, $folder); - } - } + if ($form['action_type'] == 'delete' && $trash_folder && $trash_folder != hex2bin($folder)) { + if (! $mailbox->message_action(hex2bin($folder), 'MOVE', $uids, $trash_folder)) { + $errs++; } - elseif ($form['action_type'] == 'archive' && $archive_folder && $archive_folder != hex2bin($folder)) { - /* path according to original option setting */ - if ($this->user_config->get('original_folder_setting', false)) { - $archive_folder .= '/'.hex2bin($folder); - $dest_path_exists = count($imap->get_mailbox_status($archive_folder)); - if (!$dest_path_exists) { - $imap->create_mailbox($archive_folder); - } - } - if (!$imap->message_action('MOVE', $uids, $archive_folder)) { - $errs++; + else { + foreach ($uids as $uid) { + $moved[] = sprintf("imap_%s_%s_%s", $server, $uid, $folder); } - else { - foreach ($uids as $uid) { - $moved[] = sprintf("imap_%s_%s_%s", $server, $uid, $folder); - } + } + } + elseif ($form['action_type'] == 'archive' && $archive_folder && $archive_folder != hex2bin($folder)) { + /* path according to original option setting */ + if ($this->user_config->get('original_folder_setting', false)) { + $archive_folder .= '/' . hex2bin($folder); + $dest_path_exists = count($mailbox->get_folder_status($archive_folder)); + if (!$dest_path_exists) { + $mailbox->create_folder($archive_folder); } } + if (! $mailbox->message_action(hex2bin($folder), 'MOVE', $uids, $archive_folder)) { + $errs++; + } else { - if (!$imap->message_action(mb_strtoupper($form['action_type']), $uids)) { - $errs++; + foreach ($uids as $uid) { + $moved[] = sprintf("imap_%s_%s_%s", $server, $uid, $folder); } - else { - $msgs += count($uids); - if ($form['action_type'] == 'delete') { - $imap->message_action('EXPUNGE', $uids); - } + } + } + else { + if (! $mailbox->message_action(hex2bin($folder), mb_strtoupper($form['action_type']), $uids)) { + $errs++; + } + else { + $msgs += count($uids); + if ($form['action_type'] == 'delete') { + $mailbox->message_action(hex2bin($folder), 'EXPUNGE', $uids); } } } @@ -1365,13 +1239,12 @@ public function process() { if ($success) { $ids = explode(',', $form['imap_server_ids']); foreach ($ids as $id) { - $cache = Hm_IMAP_List::get_cache($this->cache, $id); $start_time = microtime(true); - $imap = Hm_IMAP_List::connect($id, $cache); + $mailbox = Hm_IMAP_List::get_connected_mailbox($id, $this->cache); $this->out('imap_connect_time', microtime(true) - $start_time); - if (imap_authed($imap)) { - $this->out('imap_capabilities_list', $imap->get_capability()); - $this->out('imap_connect_status', $imap->get_state()); + if ($mailbox && $mailbox->authed()) { + $this->out('imap_capabilities_list', $mailbox->get_capability()); + $this->out('imap_connect_status', $mailbox->get_state()); $this->out('imap_status_server_id', $id); } else { @@ -1817,20 +1690,20 @@ public function process() { } } - $imap = false; + $mailbox = false; $cache = Hm_IMAP_List::get_cache($this->cache, $form['imap_server_id']); if ($success) { - $imap = Hm_IMAP_List::connect($form['imap_server_id'], $cache, $form['imap_user'], $form['imap_pass']); + $mailbox = Hm_IMAP_List::connect($form['imap_server_id'], $cache, $form['imap_user'], $form['imap_pass']); } elseif (isset($form['imap_server_id'])) { - $imap = Hm_IMAP_List::connect($form['imap_server_id'], $cache); + $mailbox = Hm_IMAP_List::connect($form['imap_server_id'], $cache); } - if ($imap) { - if ($imap->get_state() == 'authenticated') { - Hm_Msgs::add(sprintf("Successfully authenticated to the %s server : %s", $imap->server_type, $form['imap_user'])); + if ($mailbox) { + if ($mailbox->authed()) { + Hm_Msgs::add(sprintf("Successfully authenticated to the %s server : %s", $mailbox->server_type(), $form['imap_user'])); } else { - Hm_Msgs::add(sprintf("ERRFailed to authenticate to the %s server : %s", $imap->server_type, $form['imap_user'])); + Hm_Msgs::add(sprintf("ERRFailed to authenticate to the %s server : %s", $mailbox->server_type(), $form['imap_user'])); } } else { @@ -1900,11 +1773,11 @@ public function process() { return; } $cache = Hm_IMAP_List::get_cache($this->cache, $form['imap_server_id']); - $imap = Hm_IMAP_List::connect($form['imap_server_id'], $cache, $form['imap_user'], $form['imap_pass'], true); - if (imap_authed($imap)) { + $mailbox = Hm_IMAP_List::connect($form['imap_server_id'], $cache, $form['imap_user'], $form['imap_pass'], true); + if ($mailbox && $mailbox->authed()) { $just_saved_credentials = true; Hm_Msgs::add("Server saved"); - $this->session->record_unsaved(sprintf('%s server saved', $imap->server_type)); + $this->session->record_unsaved(sprintf('%s server saved', $mailbox->server_type())); } else { Hm_Msgs::add("ERRUnable to save this server, are the username and password correct? " . $form['imap_user']); @@ -1948,97 +1821,54 @@ public function process() { $this->out('header_allow_images', $this->config->get('allow_external_image_sources')); - $cache = Hm_IMAP_List::get_cache($this->cache, $form['imap_server_id']); - $imap = Hm_IMAP_List::connect($form['imap_server_id'], $cache); - if (imap_authed($imap)) { + $mailbox = Hm_IMAP_List::get_connected_mailbox($form['imap_server_id'], $this->cache); + if ($mailbox && $mailbox->authed()) { if ($this->user_config->get('unread_on_open_setting', false)) { - $imap->read_only = true; + $mailbox->set_read_only(true); } else { - $imap->read_only = $prefetch; - } - if ($imap->select_mailbox(hex2bin($form['folder']))) { - $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $imap->folder_state)); - $msg_struct = $imap->get_message_structure($form['imap_msg_uid']); - $this->out('msg_struct', $msg_struct); - if ($part !== false) { - if ($part == 0) { - $max = 500000; - } - else { - $max = false; - } - $struct = $imap->search_bodystructure($msg_struct, array('imap_part_number' => $part)); - $msg_struct_current = array_shift($struct); - $msg_text = $imap->get_message_content($form['imap_msg_uid'], $part, $max, $msg_struct_current); - } - else { - if (!$this->user_config->get('text_only_setting', false)) { - list($part, $msg_text) = $imap->get_first_message_part($form['imap_msg_uid'], 'text', 'html', $msg_struct); - if (!$part) { - list($part, $msg_text) = $imap->get_first_message_part($form['imap_msg_uid'], 'text', false, $msg_struct); - } - } - else { - list($part, $msg_text) = $imap->get_first_message_part($form['imap_msg_uid'], 'text', false, $msg_struct); - } - $struct = $imap->search_bodystructure( $msg_struct, array('imap_part_number' => $part)); - $msg_struct_current = array_shift($struct); - if (!trim($msg_text)) { - if (is_array($msg_struct_current) && array_key_exists('subtype', $msg_struct_current)) { - if ($msg_struct_current['subtype'] == 'plain') { - $subtype = 'html'; - } - else { - $subtype = 'plain'; - } - list($part, $msg_text) = $imap->get_first_message_part($form['imap_msg_uid'], 'text', $subtype, $msg_struct); - $struct = $imap->search_bodystructure($msg_struct, array('imap_part_number' => $part)); - $msg_struct_current = array_shift($struct); - } - } - } - if (isset($msg_struct_current['subtype']) && mb_strtolower($msg_struct_current['subtype'] == 'html')) { - $msg_text = add_attached_images($msg_text, $form['imap_msg_uid'], $msg_struct, $imap); - } - $save_reply_text = false; - if ($part == 0 || (isset($msg_struct_current['type']) && mb_strtolower($msg_struct_current['type'] == 'text'))) { - $save_reply_text = true; - } - $msg_headers = $imap->get_message_headers($form['imap_msg_uid']); - $this->out('list_headers', get_list_headers($msg_headers)); - $this->out('msg_headers', $msg_headers); - $this->out('imap_prefecth', $prefetch); - $this->out('imap_msg_part', "$part"); - $this->out('use_message_part_icons', $this->user_config->get('msg_part_icons_setting', false)); - $this->out('simple_msg_part_view', $this->user_config->get('simple_msg_parts_setting', DEFAULT_SIMPLE_MSG_PARTS)); - $this->out('allow_delete_attachment', $this->user_config->get('allow_delete_attachment_setting', false)); - if ($msg_struct_current) { - $this->out('msg_struct_current', $msg_struct_current); - } - $this->out('msg_text', $msg_text); - $download_args = sprintf("page=message&uid=%s&list_path=imap_%s_%s", $form['imap_msg_uid'], $form['imap_server_id'], $form['folder']); - $this->out('msg_download_args', $download_args.'&imap_download_message=1'); - $this->out('msg_attachment_remove_args', $download_args.'&imap_remove_attachment=1'); - $this->out('msg_show_args', sprintf("page=message&uid=%s&list_path=imap_%s_%s&imap_show_message=1", $form['imap_msg_uid'], $form['imap_server_id'], $form['folder'])); - - if ($this->get('imap_allow_images', false)) { - if ($this->module_is_supported('contacts') && $this->user_config->get('contact_auto_collect_setting', false)) { - $this->out('collect_contacts', true); - $this->out('collected_contact_email', $msg_headers["Return-Path"]); - $this->out('collected_contact_name', $msg_headers["From"]); - } + $mailbox->set_read_only($prefetch); + } + list($msg_struct, $msg_struct_current, $msg_text, $part) = $mailbox->get_structured_message(hex2bin($form['folder']), $form['imap_msg_uid'], $part, $this->user_config->get('text_only_setting', false)); + $save_reply_text = false; + if ($part == 0 || (isset($msg_struct_current['type']) && mb_strtolower($msg_struct_current['type'] == 'text'))) { + $save_reply_text = true; + } + $msg_headers = $mailbox->get_message_headers($form['imap_msg_uid']); + $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $mailbox->get_folder_status())); + $this->out('msg_struct', $msg_struct); + $this->out('list_headers', get_list_headers($msg_headers)); + $this->out('msg_headers', $msg_headers); + $this->out('imap_prefecth', $prefetch); + $this->out('imap_msg_part', "$part"); + $this->out('use_message_part_icons', $this->user_config->get('msg_part_icons_setting', false)); + $this->out('simple_msg_part_view', $this->user_config->get('simple_msg_parts_setting', DEFAULT_SIMPLE_MSG_PARTS)); + $this->out('allow_delete_attachment', $this->user_config->get('allow_delete_attachment_setting', false)); + if ($msg_struct_current) { + $this->out('msg_struct_current', $msg_struct_current); + } + $this->out('msg_text', $msg_text); + $download_args = sprintf("page=message&uid=%s&list_path=imap_%s_%s", $form['imap_msg_uid'], $form['imap_server_id'], $form['folder']); + $this->out('msg_download_args', $download_args.'&imap_download_message=1'); + $this->out('msg_attachment_remove_args', $download_args.'&imap_remove_attachment=1'); + $this->out('msg_show_args', sprintf("page=message&uid=%s&list_path=imap_%s_%s&imap_show_message=1", $form['imap_msg_uid'], $form['imap_server_id'], $form['folder'])); + + if ($this->get('imap_allow_images', false)) { + if ($this->module_is_supported('contacts') && $this->user_config->get('contact_auto_collect_setting', false)) { + $this->out('collect_contacts', true); + $this->out('collected_contact_email', $msg_headers["Return-Path"]); + $this->out('collected_contact_name', $msg_headers["From"]); } + } - if (!$prefetch) { - clear_existing_reply_details($this->session); - if ($part == 0) { - $msg_struct_current['type'] = 'text'; - $msg_struct_current['subtype'] = 'plain'; - } - $this->session->set(sprintf('reply_details_imap_%s_%s_%s', $form['imap_server_id'], $form['folder'], $form['imap_msg_uid']), - array('ts' => time(), 'msg_struct' => $msg_struct_current, 'msg_text' => ($save_reply_text ? $msg_text : ''), 'msg_headers' => $msg_headers)); + if (!$prefetch) { + clear_existing_reply_details($this->session); + if ($part == 0) { + $msg_struct_current['type'] = 'text'; + $msg_struct_current['subtype'] = 'plain'; } + $this->session->set(sprintf('reply_details_imap_%s_%s_%s', $form['imap_server_id'], $form['folder'], $form['imap_msg_uid']), + array('ts' => time(), 'msg_struct' => $msg_struct_current, 'msg_text' => ($save_reply_text ? $msg_text : ''), 'msg_headers' => $msg_headers)); } } } @@ -2054,10 +1884,9 @@ public function process() { $imap_msg_uid = $this->request->get['imap_msg_uid']; $folder = $this->request->get['imap_folder']; if ($imap_server_id && $imap_msg_uid && $folder) { - $cache = Hm_IMAP_List::get_cache($this->cache, $imap_server_id); - $imap = Hm_IMAP_List::connect($imap_server_id, $cache); - if ($imap->select_mailbox(hex2bin($folder))) { - $msg_source = $imap->get_message_content($imap_msg_uid, 0, false); + $mailbox = Hm_IMAP_List::get_connected_mailbox($imap_server_id, $this->cache); + if ($mailbox && $mailbox->authed()) { + $msg_source = $mailbox->get_message_content(hex2bin($folder), $imap_msg_uid); $this->out('msg_source', $msg_source); } } diff --git a/modules/imap/hm-ews.php b/modules/imap/hm-ews.php new file mode 100644 index 0000000000..71e74f41f3 --- /dev/null +++ b/modules/imap/hm-ews.php @@ -0,0 +1,19 @@ +load_cache($cache, 'array'); } @@ -58,6 +54,15 @@ public static function get_cache($hm_cache, $id) { $res = $hm_cache->get('imap'.$id); return $res; } + + public static function get_connected_mailbox($id, $hm_cache = null) { + if ($hm_cache) { + $cache = self::get_cache($hm_cache, $id); + } else { + $cache = false; + } + return self::connect($id, $cache); + } } /* for testing */ diff --git a/modules/imap/hm-mailbox.php b/modules/imap/hm-mailbox.php new file mode 100644 index 0000000000..a4302bb783 --- /dev/null +++ b/modules/imap/hm-mailbox.php @@ -0,0 +1,339 @@ +connection = new Hm_JMAP(); + $this->type = TYPE_JMAP; + } + elseif (array_key_exists('type', $server) && $server['type'] == 'ews') { + $this->connection = new Hm_EWS(); + $this->type = TYPE_EWS; + } + else { + $this->connection = new Hm_IMAP(); + $this->type = TYPE_IMAP; + } + return $this->connection->connect($config); + } + + public function is_imap() { + return $this->type !== TYPE_EWS; + } + + public function server_type() { + switch ($this->type) { + case TYPE_IMAP: + return 'IMAP'; + case TYPE_JMAP: + return 'JMAP'; + case TYPE_EWS: + return 'EWS'; + } + } + + public function authed() { + if ($this->is_imap()) { + return $this->connection->get_state() == 'authenticated' || $this->connection->get_state() == 'selected'; + } else { + // TODO: EWS + } + } + + public function get_folder_status($folder) { + if (! $this->authed()) { + return; + } + if ($this->is_imap()) { + return $this->connection->get_mailbox_status($folder); + } else { + // TODO: EWS + } + } + + public function create_folder($folder) { + if (! $this->authed()) { + return; + } + if ($this->is_imap()) { + return $this->connection->create_mailbox($folder); + } else { + // TODO: EWS + } + } + + public function get_folder_state() { + if ($this->is_imap()) { + return $this->connection->folder_state; + } else { + // TODO: check EWS + return true; + } + } + + public function get_selected_folder() { + if ($this->is_imap()) { + return $this->connection->selected_mailbox; + } else { + // TODO: EWS + } + } + + public function get_special_use_mailboxes($folder) { + if (! $this->authed()) { + return; + } + if ($this->is_imap()) { + return $this->connection->get_special_use_mailboxes($folder); + } else { + // TODO: EWS + } + } + + /** + * Get messages in a folder applying filters, sorting and pagination + * @return array - [total results found, results for a single page] + */ + public function get_messages($folder, $sort, $reverse, $flag_filter, $offset=0, $limit=0, $keyword=false, $trusted_senders=[]) { + if ($this->is_imap()) { + return $this->connection->get_mailbox_page($folder, $sort, $reverse, $flag_filter, $offset, $limit, $keyword, $trusted_senders); + } else { + // TODO: EWS + } + } + + public function get_message_headers($msg_id) { + return $this->connection->get_message_headers($msg_id); + } + + public function get_message_content($folder, $msg_id) { + if (! $this->authed()) { + return; + } + if ($this->is_imap()) { + if (! $this->connection->select_mailbox($folder)) { + return; + } + return $this->connection->get_message_content($msg_id, 0); + } else { + // TODO: EWS + } + } + + public function get_structured_message($folder, $msg_id, $part, $text_only) { + if ($this->is_imap()) { + if (! $this->connection->select_mailbox($folder)) { + return; + } + $msg_struct = $this->connection->get_message_structure($msg_id); + if ($part !== false) { + if ($part == 0) { + $max = 500000; + } + else { + $max = false; + } + $struct = $this->connection->search_bodystructure($msg_struct, array('imap_part_number' => $part)); + $msg_struct_current = array_shift($struct); + $msg_text = $this->connection->get_message_content($msg_id, $part, $max, $msg_struct_current); + } + else { + if (! $text_only) { + list($part, $msg_text) = $this->connection->get_first_message_part($msg_id, 'text', 'html', $msg_struct); + if (!$part) { + list($part, $msg_text) = $this->connection->get_first_message_part($msg_id, 'text', false, $msg_struct); + } + } + else { + list($part, $msg_text) = $this->connection->get_first_message_part($msg_id, 'text', false, $msg_struct); + } + $struct = $this->connection->search_bodystructure($msg_struct, array('imap_part_number' => $part)); + $msg_struct_current = array_shift($struct); + if (! trim($msg_text)) { + if (is_array($msg_struct_current) && array_key_exists('subtype', $msg_struct_current)) { + if ($msg_struct_current['subtype'] == 'plain') { + $subtype = 'html'; + } + else { + $subtype = 'plain'; + } + list($part, $msg_text) = $this->connection->get_first_message_part($msg_id, 'text', $subtype, $msg_struct); + $struct = $this->connection->search_bodystructure($msg_struct, array('imap_part_number' => $part)); + $msg_struct_current = array_shift($struct); + } + } + } + if (isset($msg_struct_current['subtype']) && mb_strtolower($msg_struct_current['subtype'] == 'html')) { + $msg_text = add_attached_images($msg_text, $msg_id, $msg_struct, $this->connection); + } + return [$msg_struct, $msg_struct_current, $msg_text, $part]; + } else { + // TODO: EWS + } + } + + public function store_message($folder, $msg) { + if (! $this->authed()) { + return false; + } + if ($this->is_imap()) { + if ($this->connection->append_start($folder, mb_strlen($msg), true)) { + $this->connection->append_feed($msg."\r\n"); + if (! $this->connection->append_end()) { + return true; + } + } + } else { + // TODO: EWS + } + return false; + } + + public function delete_message($folder, $msg_id, $trash_folder) { + if ($this->is_imap()) { + if (! $this->connection->select_mailbox($folder)) { + return false; + } + if ($trash_folder && $trash_folder != $folder) { + if ($this->connection->message_action('MOVE', [$msg_id], $trash_folder)) { + return true; + } + } + else { + if ($this->connection->message_action('DELETE', array($msg_id))) { + $this->connection->message_action('EXPUNGE', array($msg_id)); + return true; + } + } + } else { + // TODO: EWS + } + return false; + } + + public function message_action($folder, $action, $uids, $mailbox=false, $keyword=false) { + if ($this->is_imap()) { + $this->connection->select_mailbox($folder); + } + return $this->connection->message_action($action, $uids, $mailbox, $keyword); + } + + public function stream_message_part($msg_id, $part_id, $start_cb) { + if ($this->is_imap()) { + if (! $this->connection->select_mailbox($folder)) { + return; + } + $msg_struct = $this->connection->get_message_structure($msg_id); + $struct = $this->connection->search_bodystructure($msg_struct, array('imap_part_number' => $part_id)); + if (! empty($struct)) { + $part_struct = array_shift($struct); + $encoding = false; + if (array_key_exists('encoding', $part_struct)) { + $encoding = trim(mb_strtolower($part_struct['encoding'])); + } + $stream_size = $this->connection->start_message_stream($msg_id, $part_id); + if ($stream_size > 0) { + $part_name = get_imap_part_name($part_struct, $msg_id, $part_id); + $charset = ''; + if (array_key_exists('attributes', $part_struct)) { + if (is_array($part_struct['attributes']) && array_key_exists('charset', $part_struct['attributes'])) { + $charset = '; charset='.$part_struct['attributes']['charset']; + } + } + $start_cb($part_struct['type'] . '/' . $part_struct['subtype'] . $charset, $part_name); + $output_line = ''; + while($line = $this->connection->read_stream_line()) { + if ($encoding == 'quoted-printable') { + $line = quoted_printable_decode($line); + } + elseif ($encoding == 'base64') { + $line = base64_decode($line); + } + echo $output_line; + $output_line = $line; + } + if ($part_struct['type'] == 'text') { + $output_line = preg_replace("/\)(\r\n)$/m", '$1', $output_line); + } + echo $output_line; + } + } + } else { + // TODO: EWS + } + } + + public function remove_attachment($folder, $msg_id, $part_id) { + if ($this->is_imap()) { + if (! $this->connection->select_mailbox($folder)) { + return false; + } + $msg = $this->connection->get_message_content($msg_id, 0, false, false); + if ($msg) { + $attachment_id = get_attachment_id_for_mail_parser($this->connection, $msg_id, $part_id); + if ($attachment_id !== false) { + $msg = remove_attachment($attachment_id, $msg); + if ($this->connection->append_start($folder, mb_strlen($msg))) { + $this->connection->append_feed($msg."\r\n"); + if ($this->connection->append_end()) { + if ($this->connection->message_action('DELETE', array($uid))) { + $this->connection->message_action('EXPUNGE', array($uid)); + return true; + } + } + } + } + } + } else { + // TODO: EWS + } + } + + public function get_quota($folder, $root = false) { + if ($this->is_imap()) { + if ($root) { + return $this->connection->get_quota_root($folder); + } else { + return $this->connection->get_quota($folder); + } + } else { + // TODO: EWS + } + } + + public function get_debug() { + if ($this->is_imap()) { + return $this->connection->show_debug(true, true, true); + } else { + // TODO: EWS + } + } + + public function get_state() { + return $this->connection->get_state(); + } + + public function get_capability() { + return $this->connection->get_capability(); + } + + public function set_read_only($read_only) { + $this->connection->read_only = $read_only; + } +} From 26a1d9de1821e44e8b0d133af4e5bf459660761a Mon Sep 17 00:00:00 2001 From: Victor Emanouilov Date: Thu, 10 Oct 2024 11:14:13 +0300 Subject: [PATCH 2/7] imap bridge update throughout the modules and bug fixes --- modules/advanced_search/modules.php | 20 ++- modules/core/handler_modules.php | 4 +- modules/imap/functions.php | 123 ++++++++---------- modules/imap/handler_modules.php | 22 ++-- modules/imap/hm-imap.php | 1 + modules/imap/hm-mailbox.php | 195 +++++++++++++++++++++++----- modules/imap_folders/modules.php | 54 ++++---- modules/nux/modules.php | 4 +- modules/sievefilters/modules.php | 18 +-- modules/smtp/modules.php | 76 +++++------ 10 files changed, 305 insertions(+), 212 deletions(-) diff --git a/modules/advanced_search/modules.php b/modules/advanced_search/modules.php index dedfcd322b..eb976e7944 100644 --- a/modules/advanced_search/modules.php +++ b/modules/advanced_search/modules.php @@ -73,16 +73,12 @@ public function process() { $charset = $this->request->post['charset']; } - $cache = Hm_IMAP_List::get_cache($this->cache, $this->imap_id); - $imap = Hm_IMAP_List::connect($this->imap_id, $cache); - if (!imap_authed($imap)) { - return; - } - if (!$imap->select_mailbox($this->folder)) { + $mailbox = Hm_IMAP_List::get_connected_mailbox($this->imap_id, $this->cache); + if (! $mailbox || ! $mailbox->authed()) { return; } if ($charset) { - $imap->search_charset = $charset; + $mailbox->set_search_charset($charset); } $params = array( array('SENTBEFORE', date('j-M-Y', strtotime($form['adv_end']))), @@ -93,23 +89,23 @@ public function process() { $params[] = array($target, $term); } } - $this->out('imap_search_results', $this->imap_search($flags, $imap, $params, $limit)); - $this->out('folder_status', $imap->folder_state); + $this->out('imap_search_results', $this->imap_search($flags, $mailbox, $params, $limit)); + $this->out('folder_status', $mailbox->get_folder_state()); $this->out('imap_server_ids', array($this->imap_id)); } - private function imap_search($flags, $imap, $params, $limit) { + private function imap_search($flags, $mailbox, $params, $limit) { $msg_list = array(); $exclude_deleted = true; if (in_array('deleted', $flags, true)) { $exclude_deleted = false; } - $msgs = $imap->search($flags, false, $params, array(), $exclude_deleted); + $msgs = $mailbox->search($this->folder, $flags, false, $params, array(), $exclude_deleted); if (!$msgs) { return $msg_list; } $server_details = Hm_IMAP_List::dump($this->imap_id); - foreach ($imap->get_message_list($msgs) as $msg) { + foreach ($mailbox->get_message_list($this->folder, $msgs) as $msg) { if (array_key_exists('content-type', $msg) && mb_stristr($msg['content-type'], 'multipart/mixed')) { $msg['flags'] .= ' \Attachment'; } diff --git a/modules/core/handler_modules.php b/modules/core/handler_modules.php index c0e4aa2031..bb46a4132e 100644 --- a/modules/core/handler_modules.php +++ b/modules/core/handler_modules.php @@ -60,8 +60,8 @@ public function process() { $current['pass'] = $form['password']; unset($current['nopass']); Hm_IMAP_List::edit($server['id'], $current); - $imap = Hm_IMAP_List::connect($server['id'], false); - if ($imap->get_state() == 'authenticated') { + $mailbox = Hm_IMAP_List::connect($server['id'], false); + if ($mailbox && $mailbox->authed()) { Hm_Msgs::add('Password Updated'); $this->out('connect_status', true); } diff --git a/modules/imap/functions.php b/modules/imap/functions.php index ed2b80d00a..c6812cf7f6 100644 --- a/modules/imap/functions.php +++ b/modules/imap/functions.php @@ -727,13 +727,12 @@ function merge_imap_search_results($ids, $search_type, $session, $hm_cache, $fol $sent_results = array(); $status = array(); foreach($ids as $index => $id) { - $cache = Hm_IMAP_List::get_cache($hm_cache, $id); - $imap = Hm_IMAP_List::connect($id, $cache); - if (imap_authed($imap)) { + $mailbox = Hm_IMAP_List::get_connected_mailbox($id, $hm_cache); + if ($mailbox && $mailbox->authed()) { $server_details = Hm_IMAP_List::dump($id); $folder = $folders[$index]; if ($sent) { - $sent_folder = $imap->get_special_use_mailboxes('sent'); + $sent_folder = $mailbox->get_special_use_mailboxes('sent'); if (array_key_exists('sent', $sent_folder)) { list($sent_status, $sent_results) = merge_imap_search_results($ids, $search_type, $session, $hm_cache, array($sent_folder['sent']), $limit, $terms, false); $status = array_merge($status, $sent_status); @@ -742,44 +741,42 @@ function merge_imap_search_results($ids, $search_type, $session, $hm_cache, $fol continue; } } - if ($imap->select_mailbox($folder)) { - $status['imap_'.$id.'_'.bin2hex($folder)] = $imap->folder_state; - if (!empty($terms)) { - foreach ($terms as $term) { - if (preg_match('/(?:[^\x00-\x7F])/', $term[1]) === 1) { - $imap->search_charset = 'UTF-8'; - break; - } - } - if ($sent) { - $msgs = $imap->search($search_type, false, $terms, array(), true, false, true); - } - else { - $msgs = $imap->search($search_type, false, $terms); + if (!empty($terms)) { + foreach ($terms as $term) { + if (preg_match('/(?:[^\x00-\x7F])/', $term[1]) === 1) { + $mailbox->set_search_charset('UTF-8'); + break; } } + if ($sent) { + $msgs = $mailbox->search($folder, $search_type, false, $terms, array(), true, false, true); + } else { - $msgs = $imap->search($search_type); + $msgs = $mailbox->search($folder, $search_type, false, $terms); + } + } + else { + $msgs = $mailbox->search($folder, $search_type); + } + if ($msgs) { + if ($limit) { + rsort($msgs); + $msgs = array_slice($msgs, 0, $limit); } - if ($msgs) { - if ($limit) { - rsort($msgs); - $msgs = array_slice($msgs, 0, $limit); + foreach ($mailbox->get_message_list($folder, $msgs) as $msg) { + if (array_key_exists('content-type', $msg) && mb_stristr($msg['content-type'], 'multipart/mixed')) { + $msg['flags'] .= ' \Attachment'; } - foreach ($imap->get_message_list($msgs) as $msg) { - if (array_key_exists('content-type', $msg) && mb_stristr($msg['content-type'], 'multipart/mixed')) { - $msg['flags'] .= ' \Attachment'; - } - if (mb_stristr($msg['flags'], 'deleted')) { - continue; - } - $msg['server_id'] = $id; - $msg['folder'] = bin2hex($folder); - $msg['server_name'] = $server_details['name']; - $msg_list[] = $msg; + if (mb_stristr($msg['flags'], 'deleted')) { + continue; } + $msg['server_id'] = $id; + $msg['folder'] = bin2hex($folder); + $msg['server_name'] = $server_details['name']; + $msg_list[] = $msg; } } + $status['imap_'.$id.'_'.bin2hex($folder)] = $mailbox->get_folder_state(); } else { $connection_failed = true; @@ -861,23 +858,22 @@ function imap_move_same_server($ids, $action, $hm_cache, $dest_path, $screen_ema $moved = array(); $keys = array_keys($ids); $server_id = array_pop($keys); - $cache = Hm_IMAP_List::get_cache($hm_cache, $server_id); - $imap = Hm_IMAP_List::connect($server_id, $cache); - foreach ($ids[$server_id] as $folder => $msgs) { - if (imap_authed($imap) && $imap->select_mailbox(hex2bin($folder))) { + $mailbox = Hm_IMAP_List::get_connected_mailbox($server_id, $hm_cache); + if ($mailbox && $mailbox->authed()) { + foreach ($ids[$server_id] as $folder => $msgs) { if ($screen_emails) { foreach ($msgs as $msg) { $moved[] = sprintf('imap_%s_%s_%s', $server_id, $msg, $folder); - $email = current(array_column(process_address_fld($imap->get_message_headers($msg)['From']), "email")); - $uids = $imap->search('ALL', false, array(array('FROM', $email))); + $email = current(array_column(process_address_fld($mailbox->get_message_headers(hex2bin($folder), $msg)['From']), "email")); + $uids = $mailbox->search(hex2bin($folder), 'ALL', false, array(array('FROM', $email))); foreach ($uids as $uid) { - if ($imap->message_action(mb_strtoupper($action), $uid, hex2bin($dest_path[2]))) { + if ($mailbox->message_action(hex2bin($folder), mb_strtoupper($action), $uid, hex2bin($dest_path[2]))) { $moved[] = sprintf('imap_%s_%s_%s', $server_id, $uid, $folder); } } } } else { - if ($imap->message_action(mb_strtoupper($action), $msgs, hex2bin($dest_path[2]))) { + if ($mailbox->message_action(hex2bin($folder), mb_strtoupper($action), $msgs, hex2bin($dest_path[2]))) { foreach ($msgs as $msg) { $moved[] = sprintf('imap_%s_%s_%s', $server_id, $msg, $folder); } @@ -901,16 +897,14 @@ function imap_move_same_server($ids, $action, $hm_cache, $dest_path, $screen_ema if (!hm_exists('imap_move_different_server')) { function imap_move_different_server($ids, $action, $dest_path, $hm_cache) { $moved = array(); - $cache = Hm_IMAP_List::get_cache($hm_cache, $dest_path[1]); - $dest_imap = Hm_IMAP_List::connect($dest_path[1], $cache); - if ($dest_imap) { + $dest_mailbox = Hm_IMAP_List::get_connected_mailbox($dest_path[1], $hm_cache); + if ($dest_mailbox && $dest_mailbox->authed()) { foreach ($ids as $server_id => $folders) { - $cache = Hm_IMAP_List::get_cache($hm_cache, $server_id); - $imap = Hm_IMAP_List::connect($server_id, $cache); - foreach ($folders as $folder => $msg_ids) { - if (imap_authed($imap) && $imap->select_mailbox(hex2bin($folder))) { + $mailbox = Hm_IMAP_List::get_connected_mailbox($server_id, $hm_cache); + if ($mailbox && $mailbox->authed()) { + foreach ($folders as $folder => $msg_ids) { foreach ($msg_ids as $msg_id) { - $detail = $imap->get_message_list(array($msg_id)); + $detail = $mailbox->get_message_list(hex2bin($folder), array($msg_id)); if (array_key_exists($msg_id, $detail)) { if (mb_stristr($detail[$msg_id]['flags'], 'seen')) { $seen = true; @@ -919,23 +913,20 @@ function imap_move_different_server($ids, $action, $dest_path, $hm_cache) { $seen = false; } } - $msg = $imap->get_message_content($msg_id, 0); + $msg = $mailbox->get_message_content(hex2bin($folder), $msg_id); $msg = str_replace("\r\n", "\n", $msg); $msg = str_replace("\n", "\r\n", $msg); $msg = rtrim($msg)."\r\n"; if (!$seen) { - $imap->message_action('UNREAD', array($msg_id)); + $mailbox->message_action(hex2bin($folder), 'UNREAD', array($msg_id)); } - if ($dest_imap->append_start(hex2bin($dest_path[2]), mb_strlen($msg), $seen)) { - $dest_imap->append_feed($msg."\r\n"); - if ($dest_imap->append_end()) { - if ($action == 'move') { - if ($imap->message_action('DELETE', array($msg_id))) { - $imap->message_action('EXPUNGE', array($msg_id)); - } + if ($dest_mailbox->store_message(hex2bin($dest_path[2]), $msg, $seen)) { + if ($action == 'move') { + if ($mailbox->message_action(hex2bin($folder), 'DELETE', array($msg_id))) { + $mailbox->message_action(hex2bin($folder), 'EXPUNGE', array($msg_id)); } - $moved[] = sprintf('imap_%s_%s_%s', $server_id, $msg_id, $folder); } + $moved[] = sprintf('imap_%s_%s_%s', $server_id, $msg_id, $folder); } } } @@ -1217,14 +1208,14 @@ function decode_folder_str($folder) { * @subpackage imap/functions */ if (!hm_exists('prep_folder_name')) { -function prep_folder_name($imap, $folder, $decode_folder=false, $parent=false) { +function prep_folder_name($mailbox, $folder, $decode_folder=false, $parent=false) { if ($parent && $decode_folder) { $parent = decode_folder_str($parent); } if ($decode_folder) { $folder = decode_folder_str($folder); } - $ns = get_personal_ns($imap); + $ns = get_personal_ns($mailbox); if (!$folder) { return false; } @@ -1244,8 +1235,8 @@ function prep_folder_name($imap, $folder, $decode_folder=false, $parent=false) { * @subpackage imap/functions */ if (!hm_exists('get_personal_ns')) { -function get_personal_ns($imap) { - $namespaces = $imap->get_namespaces(); +function get_personal_ns($mailbox) { + $namespaces = $mailbox->get_namespaces(); foreach ($namespaces as $ns) { if ($ns['class'] == 'personal') { return $ns; @@ -1561,9 +1552,9 @@ function connect_to_imap_server($address, $name, $port, $user, $pass, $tls, $ima } } - $imap = Hm_IMAP_List::connect($imap_server_id, false); + $mailbox = Hm_IMAP_List::connect($imap_server_id, false); - if (imap_authed($imap)) { + if ($mailbox->authed()) { return $imap_server_id; }else { Hm_IMAP_List::del($imap_server_id); diff --git a/modules/imap/handler_modules.php b/modules/imap/handler_modules.php index 5eb4ca8cc1..d69e80cdcf 100644 --- a/modules/imap/handler_modules.php +++ b/modules/imap/handler_modules.php @@ -353,7 +353,7 @@ public function process() { $uid = null; $mailbox_page = $mailbox->get_messages($sent_folder, 'ARRIVAL', true, 'ALL', 0, 10); foreach ($mailbox_page[1] as $mail) { - $msg_header = $mailbox->get_message_headers($mail['uid']); + $msg_header = $mailbox->get_message_headers($sent_folder, $mail['uid']); if ($msg_header['Message-Id'] === $mime->get_headers()['Message-Id']) { $uid = $mail['uid']; break; @@ -677,12 +677,12 @@ public function process() { $this->out('with_input', $with_subscription); return; } - if (imap_authed($imap)) { + if ($mailbox && $mailbox->authed()) { $only_subscribed = $this->user_config->get('only_subscribed_folders_setting', false); if ($with_subscription) { $only_subscribed = false; } - $msgs = $imap->get_folder_list_by_level(hex2bin($folder), $only_subscribed, $with_subscription); + $msgs = $mailbox->get_subfolders(hex2bin($folder), $only_subscribed, $with_subscription); if (isset($msgs[$folder])) { unset($msgs[$folder]); } @@ -693,7 +693,7 @@ public function process() { $this->out('with_input', $with_subscription); } else { - Hm_Msgs::add(sprintf('ERRCould not authenticate to the selected %s server (%s)', $imap->server_type, $this->user_config->get('imap_servers')[$form['imap_server_id']]['user'])); + Hm_Msgs::add(sprintf('ERRCould not authenticate to the selected %s server (%s)', $mailbox->server_type(), $this->user_config->get('imap_servers')[$form['imap_server_id']]['user'])); } } } @@ -758,7 +758,7 @@ public function process() { $folder['detail']['exists'] = $total; $this->out('imap_folder_detail', array_merge($folder, array('offset' => $offset, 'limit' => $limit))); } - $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $mailbox->get_folder_status())); + $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $mailbox->get_folder_state())); } $this->out('imap_mailbox_page', $msgs); $this->out('list_page', $list_page); @@ -809,7 +809,7 @@ public function process() { if ($mailbox->delete_message(hex2bin($form['folder']), $form['imap_msg_uid'], $trash_folder)) { $del_result = true; } - $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $mailbox->get_folder_status())); + $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $mailbox->get_folder_state())); } if (!$del_result) { Hm_Msgs::add('ERRAn error occurred trying to delete this message'); @@ -913,7 +913,7 @@ public function process() { if ($mailbox->message_action(hex2bin($form['folder']), $cmd, array($form['imap_msg_uid']))) { $flag_result = true; } - $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $mailbox->get_folder_status())); + $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $mailbox->get_folder_state())); } if (!$flag_result) { Hm_Msgs::add('ERRAn error occurred trying to flag this message'); @@ -1023,7 +1023,7 @@ public function process() { } $ret = $mailbox->get_messages($folder, 'DATE', false, 'ALL'); foreach ($ret[1] as $msg) { - $msg_headers = $mailbox->get_message_headers($msg['uid']); + $msg_headers = $mailbox->get_message_headers($folder, $msg['uid']); if (isset($msg_headers['X-Snoozed'])) { try { $snooze_headers = parse_snooze_header($msg_headers['X-Snoozed']); @@ -1389,7 +1389,7 @@ public function process() { $cache = array(); foreach ($servers as $index => $server) { if (isset($server['object']) && is_object($server['object'])) { - if ($server['object']->use_cache) { + if ($server['object']->use_cache()) { $cache[$index] = $server['object']->dump_cache('array'); } } @@ -1834,8 +1834,8 @@ public function process() { if ($part == 0 || (isset($msg_struct_current['type']) && mb_strtolower($msg_struct_current['type'] == 'text'))) { $save_reply_text = true; } - $msg_headers = $mailbox->get_message_headers($form['imap_msg_uid']); - $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $mailbox->get_folder_status())); + $msg_headers = $mailbox->get_message_headers(hex2bin($form['folder']), $form['imap_msg_uid']); + $this->out('folder_status', array('imap_'.$form['imap_server_id'].'_'.$form['folder'] => $mailbox->get_folder_state())); $this->out('msg_struct', $msg_struct); $this->out('list_headers', get_list_headers($msg_headers)); $this->out('msg_headers', $msg_headers); diff --git a/modules/imap/hm-imap.php b/modules/imap/hm-imap.php index a2947c6fb7..d4c9cf742a 100644 --- a/modules/imap/hm-imap.php +++ b/modules/imap/hm-imap.php @@ -12,6 +12,7 @@ require_once('hm-imap-bodystructure.php'); require_once('hm-jmap.php'); require_once('hm-ews.php'); +require_once('hm-mailbox.php'); /** * IMAP connection manager diff --git a/modules/imap/hm-mailbox.php b/modules/imap/hm-mailbox.php index a4302bb783..4bdadeb07e 100644 --- a/modules/imap/hm-mailbox.php +++ b/modules/imap/hm-mailbox.php @@ -20,30 +20,30 @@ class Hm_Mailbox { public function connect(array $config) { if (array_key_exists('type', $config) && $config['type'] == 'jmap') { $this->connection = new Hm_JMAP(); - $this->type = TYPE_JMAP; + $this->type = self::TYPE_JMAP; } - elseif (array_key_exists('type', $server) && $server['type'] == 'ews') { + elseif (array_key_exists('type', $config) && $config['type'] == 'ews') { $this->connection = new Hm_EWS(); - $this->type = TYPE_EWS; + $this->type = self::TYPE_EWS; } else { $this->connection = new Hm_IMAP(); - $this->type = TYPE_IMAP; + $this->type = self::TYPE_IMAP; } return $this->connection->connect($config); } public function is_imap() { - return $this->type !== TYPE_EWS; + return $this->type !== self::TYPE_EWS; } public function server_type() { switch ($this->type) { - case TYPE_IMAP: + case self::TYPE_IMAP: return 'IMAP'; - case TYPE_JMAP: + case self::TYPE_JMAP: return 'JMAP'; - case TYPE_EWS: + case self::TYPE_EWS: return 'EWS'; } } @@ -78,6 +78,61 @@ public function create_folder($folder) { } } + public function rename_folder($folder, $new_name) { + if (! $this->authed()) { + return; + } + if ($this->is_imap()) { + return $this->connection->rename_mailbox($folder, $new_name); + } else { + // TODO: EWS + } + } + + public function delete_folder($folder) { + if (! $this->authed()) { + return; + } + if ($this->is_imap()) { + return $this->connection->delete_mailbox($folder); + } else { + // TODO: EWS + } + } + + public function folder_subscription($folder, $action) { + if (! $this->authed()) { + return; + } + if ($this->is_imap()) { + return $this->connection->mailbox_subscription($folder, $action); + } else { + // TODO: EWS + } + } + + public function get_folders($only_subscribed = false) { + if (! $this->authed()) { + return; + } + if ($this->is_imap()) { + return $this->connection->get_mailbox_list($only_subscribed); + } else { + // TODO: EWS + } + } + + public function get_subfolders($folder, $only_subscribed = false, $with_input = false) { + if (! $this->authed()) { + return; + } + if ($this->is_imap()) { + return $this->connection->get_folder_list_by_level($folder, $only_subscribed, $with_input); + } else { + // TODO: EWS + } + } + public function get_folder_state() { if ($this->is_imap()) { return $this->connection->folder_state; @@ -95,7 +150,7 @@ public function get_selected_folder() { } } - public function get_special_use_mailboxes($folder) { + public function get_special_use_mailboxes($folder = false) { if (! $this->authed()) { return; } @@ -118,29 +173,37 @@ public function get_messages($folder, $sort, $reverse, $flag_filter, $offset=0, } } - public function get_message_headers($msg_id) { - return $this->connection->get_message_headers($msg_id); + public function get_message_headers($folder, $msg_id) { + if (! $this->select_folder($folder)) { + return; + } + if ($this->is_imap()) { + return $this->connection->get_message_headers($msg_id); + } else { + // TODO: EWS + } + } - public function get_message_content($folder, $msg_id) { + public function get_message_content($folder, $msg_id, $part = 0) { if (! $this->authed()) { return; } + if (! $this->select_folder($folder)) { + return; + } if ($this->is_imap()) { - if (! $this->connection->select_mailbox($folder)) { - return; - } - return $this->connection->get_message_content($msg_id, 0); + return $this->connection->get_message_content($msg_id, $part); } else { // TODO: EWS } } public function get_structured_message($folder, $msg_id, $part, $text_only) { + if (! $this->select_folder($folder)) { + return; + } if ($this->is_imap()) { - if (! $this->connection->select_mailbox($folder)) { - return; - } $msg_struct = $this->connection->get_message_structure($msg_id); if ($part !== false) { if ($part == 0) { @@ -188,12 +251,12 @@ public function get_structured_message($folder, $msg_id, $part, $text_only) { } } - public function store_message($folder, $msg) { + public function store_message($folder, $msg, $seen = true, $draft = false) { if (! $this->authed()) { return false; } if ($this->is_imap()) { - if ($this->connection->append_start($folder, mb_strlen($msg), true)) { + if ($this->connection->append_start($folder, mb_strlen($msg), $seen, $draft)) { $this->connection->append_feed($msg."\r\n"); if (! $this->connection->append_end()) { return true; @@ -206,10 +269,10 @@ public function store_message($folder, $msg) { } public function delete_message($folder, $msg_id, $trash_folder) { + if (! $this->select_folder($folder)) { + return; + } if ($this->is_imap()) { - if (! $this->connection->select_mailbox($folder)) { - return false; - } if ($trash_folder && $trash_folder != $folder) { if ($this->connection->message_action('MOVE', [$msg_id], $trash_folder)) { return true; @@ -228,17 +291,17 @@ public function delete_message($folder, $msg_id, $trash_folder) { } public function message_action($folder, $action, $uids, $mailbox=false, $keyword=false) { - if ($this->is_imap()) { - $this->connection->select_mailbox($folder); + if (! $this->select_folder($folder)) { + return; } return $this->connection->message_action($action, $uids, $mailbox, $keyword); } - public function stream_message_part($msg_id, $part_id, $start_cb) { + public function stream_message_part($folder, $msg_id, $part_id, $start_cb) { + if (! $this->select_folder($folder)) { + return; + } if ($this->is_imap()) { - if (! $this->connection->select_mailbox($folder)) { - return; - } $msg_struct = $this->connection->get_message_structure($msg_id); $struct = $this->connection->search_bodystructure($msg_struct, array('imap_part_number' => $part_id)); if (! empty($struct)) { @@ -280,10 +343,10 @@ public function stream_message_part($msg_id, $part_id, $start_cb) { } public function remove_attachment($folder, $msg_id, $part_id) { + if (! $this->select_folder($folder)) { + return; + } if ($this->is_imap()) { - if (! $this->connection->select_mailbox($folder)) { - return false; - } $msg = $this->connection->get_message_content($msg_id, 0, false, false); if ($msg) { $attachment_id = get_attachment_id_for_mail_parser($this->connection, $msg_id, $part_id); @@ -325,6 +388,24 @@ public function get_debug() { } } + public function use_cache() { + if ($this->is_imap()) { + return $this->connection->use_cache; + } else { + // TODO: check EWS caching + return false; + } + } + + public function dump_cache($type = 'string') { + if ($this->is_imap()) { + return $this->connection->dump_cache($type); + } else { + // TODO: check EWS caching + return; + } + } + public function get_state() { return $this->connection->get_state(); } @@ -333,7 +414,53 @@ public function get_capability() { return $this->connection->get_capability(); } + public function get_namespaces() { + return $this->connection->get_namespaces(); + } + public function set_read_only($read_only) { - $this->connection->read_only = $read_only; + if ($this->is_imap()) { + $this->connection->read_only = $read_only; + } + } + + public function set_search_charset($charset) { + if ($this->is_imap()) { + $this->connection->search_charset = $charset; + } + } + + public function search($folder, $target='ALL', $uids=false, $terms=array(), $esearch=array(), $exclude_deleted=true, $exclude_auto_bcc=true, $only_auto_bcc=false) { + if (! $this->select_folder($folder)) { + return; + } + if ($this->is_imap()) { + return $this->connection->search($target, $uids, $terms, $esearch, $exclude_deleted, $exclude_auto_bcc, $only_auto_bcc); + } else { + // TODO: EWS + } + } + + public function get_message_list($folder, $msg_ids) { + if (! $this->select_folder($folder)) { + return; + } + if ($this->is_imap()) { + return $this->connection->get_message_list($msg_ids); + } else { + // TODO: EWS + } + } + + protected function select_folder($folder) { + if ($this->is_imap()) { + if (isset($this->connection->selected_mailbox['name']) && $this->connection->selected_mailbox['name'] == $folder) { + return true; + } + if (! $this->connection->select_mailbox($folder)) { + return false; + } + } + return true; } } diff --git a/modules/imap_folders/modules.php b/modules/imap_folders/modules.php index df6985abac..275ecc1779 100644 --- a/modules/imap_folders/modules.php +++ b/modules/imap_folders/modules.php @@ -98,15 +98,13 @@ public function process() { if (!$success || !in_array($form['special_folder_type'], array('sent', 'draft', 'trash', 'archive', 'junk'), true)) { return; } - $cache = Hm_IMAP_List::get_cache($this->cache, $form['imap_server_id']); - $imap = Hm_IMAP_List::connect($form['imap_server_id'], $cache); - - if (!is_object($imap) || $imap->get_state() != 'authenticated') { + $mailbox = Hm_IMAP_List::get_connected_mailbox($form['imap_server_id'], $this->cache); + if (!is_object($mailbox) || ! $mailbox->authed()) { Hm_Msgs::add('ERRUnable to connect to the selected IMAP server'); return; } - $new_folder = prep_folder_name($imap, $form['folder'], true); - if (!$new_folder || !$imap->select_mailbox($new_folder)) { + $new_folder = prep_folder_name($mailbox, $form['folder'], true); + if (! $new_folder || ! $mailbox->get_folder_status($new_folder)) { Hm_Msgs::add('ERRSelected folder not found'); return; } @@ -132,15 +130,13 @@ public function process() { list($success, $form) = $this->process_form(array('imap_server_id', 'imap_service_name')); if ($success) { - $cache = Hm_IMAP_List::get_cache($this->cache, $form['imap_server_id']); - $imap = Hm_IMAP_List::connect($form['imap_server_id'], $cache); - - if (!is_object($imap) || $imap->get_state() != 'authenticated') { + $mailbox = Hm_IMAP_List::get_connected_mailbox($form['imap_server_id'], $this->cache); + if (!is_object($mailbox) || ! $mailbox->authed()) { Hm_Msgs::add('ERRUnable to connect to the selected IMAP server'); return; } $specials = $this->user_config->get('special_imap_folders', array()); - $exposed = $imap->get_special_use_mailboxes(); + $exposed = $mailbox->get_special_use_mailboxes(); if ($form['imap_service_name'] == 'gandi') { $specials[$form['imap_server_id']] = array( 'sent' => 'Sent', @@ -182,11 +178,10 @@ public function process() { if (array_key_exists('parent', $this->request->post) && trim($this->request->post['parent'])) { $parent_str = decode_folder_str($this->request->post['parent']); } - $cache = Hm_IMAP_List::get_cache($this->cache, $form['imap_server_id']); - $imap = Hm_IMAP_List::connect($form['imap_server_id'], $cache); - if (is_object($imap) && $imap->get_state() == 'authenticated') { - $new_folder = prep_folder_name($imap, $form['folder'], false, $parent_str); - if ($new_folder && $imap->create_mailbox($new_folder)) { + $mailbox = Hm_IMAP_List::get_connected_mailbox($form['imap_server_id'], $this->cache); + if ($mailbox && $mailbox->authed()) { + $new_folder = prep_folder_name($mailbox, $form['folder'], false, $parent_str); + if ($new_folder && $mailbox->create_folder($new_folder)) { Hm_Msgs::add('Folder created'); $this->cache->del('imap_folders_imap_'.$form['imap_server_id'].'_'); $this->out('imap_folders_success', true); @@ -206,16 +201,15 @@ class Hm_Handler_process_folder_rename extends Hm_Handler_Module { public function process() { list($success, $form) = $this->process_form(array('imap_server_id', 'folder', 'new_folder')); if ($success) { - $cache = Hm_IMAP_List::get_cache($this->cache, $form['imap_server_id']); - $imap = Hm_IMAP_List::connect($form['imap_server_id'], $cache); $parent_str = false; if (array_key_exists('parent', $this->request->post)) { $parent_str = $this->request->post['parent']; } - if (is_object($imap) && $imap->get_state() == 'authenticated') { - $old_folder = prep_folder_name($imap, $form['folder'], true); - $new_folder = prep_folder_name($imap, $form['new_folder'], false, $parent_str); - if ($new_folder && $old_folder && $imap->rename_mailbox($old_folder, $new_folder)) { + $mailbox = Hm_IMAP_List::get_connected_mailbox($form['imap_server_id'], $this->cache); + if ($mailbox && $mailbox->authed()) { + $old_folder = prep_folder_name($mailbox, $form['folder'], true); + $new_folder = prep_folder_name($mailbox, $form['new_folder'], false, $parent_str); + if ($new_folder && $old_folder && $mailbox->rename_mailbox($old_folder, $new_folder)) { if ($this->module_is_supported('sievefilters') && $this->user_config->get('enable_sieve_filter_setting', DEFAULT_ENABLE_SIEVE_FILTER)) { $imap_servers = $this->user_config->get('imap_servers'); $imap_account = $imap_servers[$form['imap_server_id']]; @@ -271,17 +265,16 @@ class Hm_Handler_process_folder_delete extends Hm_Handler_Module { public function process() { list($success, $form) = $this->process_form(array('imap_server_id', 'folder')); if ($success) { - $cache = Hm_IMAP_List::get_cache($this->cache, $form['imap_server_id']); - $imap = Hm_IMAP_List::connect($form['imap_server_id'], $cache); - if (is_object($imap) && $imap->get_state() == 'authenticated') { - $del_folder = prep_folder_name($imap, $form['folder'], true); + $mailbox = Hm_IMAP_List::get_connected_mailbox($form['imap_server_id'], $this->cache); + if ($mailbox && $mailbox->authed()) { + $del_folder = prep_folder_name($mailbox, $form['folder'], true); if ($this->module_is_supported('sievefilters') && $this->user_config->get('enable_sieve_filter_setting', DEFAULT_ENABLE_SIEVE_FILTER)) { if (is_mailbox_linked_with_filters($del_folder, $form['imap_server_id'], $this)) { Hm_Msgs::add('ERRThis folder can\'t be deleted because it is used in a filter.'); return; } } - if ($del_folder && $imap->delete_mailbox($del_folder)) { + if ($del_folder && $mailbox->delete_folder($del_folder)) { Hm_Msgs::add('Folder deleted'); $this->cache->del('imap_folders_imap_'.$form['imap_server_id'].'_'); $this->out('imap_folders_success', true); @@ -344,11 +337,10 @@ public function process() { list($success, $form) = $this->process_form(array('folder', 'subscription_state')); if ($success) { $imap_server_id = $this->request->get['imap_server_id']; - $cache = Hm_IMAP_List::get_cache($this->cache, $imap_server_id); - $imap = Hm_IMAP_List::connect($imap_server_id, $cache); - if (imap_authed($imap)) { + $mailbox = Hm_IMAP_List::get_connected_mailbox($imap_server_id, $this->cache); + if ($mailbox && $mailbox->authed()) { $folder = hex2bin($form['folder']); - $success = $imap->mailbox_subscription($folder, $form['subscription_state']); + $success = $mailbox->folder_subscription($folder, $form['subscription_state']); if ($success) { Hm_Msgs::add(sprintf('%s to %s', $form['subscription_state']? 'Subscribed': 'Unsubscribed', $folder)); $this->cache->del('imap_folders_imap_'.$imap_server_id.'_'); diff --git a/modules/nux/modules.php b/modules/nux/modules.php index 936e7a73c0..36bd97cdc7 100644 --- a/modules/nux/modules.php +++ b/modules/nux/modules.php @@ -179,8 +179,8 @@ public function process() { if (! can_save_last_added_server('Hm_IMAP_List', $form['nux_email'])) { return; } - $imap = Hm_IMAP_List::connect($new_id, false); - if ($imap && $imap->get_state() == 'authenticated') { + $mailbox = Hm_IMAP_List::connect($new_id, false); + if ($mailbox && $mailbox->authed()) { if (isset($details['smtp'])) { Hm_SMTP_List::add(array( 'name' => $details['name'], diff --git a/modules/sievefilters/modules.php b/modules/sievefilters/modules.php index dd34d04c06..a42628d152 100644 --- a/modules/sievefilters/modules.php +++ b/modules/sievefilters/modules.php @@ -291,14 +291,13 @@ public function process() { $imap_server_id = $idx; } } - $cache = Hm_IMAP_List::get_cache($this->cache, $imap_server_id); - $imap = Hm_IMAP_List::connect($imap_server_id, $cache); - if (!imap_authed($imap)) { + $mailbox = Hm_IMAP_List::get_connected_mailbox($imap_server_id, $this->cache); + if (! $mailbox || ! $mailbox->authed()) { Hm_Msgs::add('ERRIMAP Authentication Failed'); return; } $mailboxes = []; - foreach ($imap->get_mailbox_list() as $idx => $mailbox) { + foreach ($mailbox->get_folders() as $idx => $mailbox) { $mailboxes[] = $mailbox['name']; } $this->out('mailboxes', json_encode($mailboxes)); @@ -452,17 +451,12 @@ public function process() { } if (isset($this->request->post['imap_msg_uid'])) { - $imap = Hm_IMAP_List::connect($this->request->post['imap_server_id']); - - if (!imap_authed($imap)) { + $mailbox = Hm_IMAP_List::get_connected_mailbox($this->request->post['imap_server_id'], $this->cache); + if (! $mailbox || ! $mailbox->authed()) { Hm_Msgs::add('ERRIMAP Authentication Failed'); return; } - if (!$imap->select_mailbox(hex2bin($this->request->post['folder']))) { - Hm_Msgs::add('ERRIMAP Mailbox select error'); - return; - } - $msg_header = $imap->get_message_headers($form['imap_msg_uid']); + $msg_header = $mailbox->get_message_headers(hex2bin($this->request->post['folder']), $form['imap_msg_uid']); $test_pattern = "/(?:[a-z0-9!#$%&'*+=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+=?^_`{|}~-]+)*|\"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/"; preg_match_all($test_pattern, $msg_header['From'], $email_sender); $email_sender = $email_sender[0][0]; diff --git a/modules/smtp/modules.php b/modules/smtp/modules.php index e82b7587db..7dbdef5cd4 100644 --- a/modules/smtp/modules.php +++ b/modules/smtp/modules.php @@ -56,11 +56,10 @@ public function process() { && array_key_exists('list_path', $this->request->get) && array_key_exists('uid', $this->request->get)) { $path = explode('_', $this->request->get['list_path']); - $imap = Hm_IMAP_List::connect($path[1]); - if ($imap->select_mailbox(hex2bin($path[2]))) { - $msg_struct = $imap->get_message_structure($this->request->get['uid']); - list($part, $msg_text) = $imap->get_first_message_part($this->request->get['uid'], 'text', 'plain', $msg_struct); - $msg_header = $imap->get_message_headers($this->request->get['uid']); + $mailbox = Hm_IMAP_List::get_connected_mailbox($path[1]); + if ($mailbox && $mailbox->authed()) { + list ($msg_struct, $msg_struct_first, $msg_text, $part) = $mailbox->get_structured_message(hex2bin($path[2]), $this->request->get['uid'], false, true); + $msg_header = $mailbox->get_message_headers(hex2bin($path[2]), $this->request->get['uid']); if (!array_key_exists('From', $msg_header) || count($msg_header) == 0) { return; } @@ -78,7 +77,7 @@ public function process() { $new_attachment['size'] = $sub['size']; $new_attachment['type'] = $sub['type']; $file_path = $this->config->get('attachment_dir').DIRECTORY_SEPARATOR.$new_attachment['name']; - $content = Hm_Crypt::ciphertext($imap->get_message_content($this->request->get['uid'], $ind), Hm_Request_Key::generate()); + $content = Hm_Crypt::ciphertext($mailbox->get_message_content(hex2bin($path[2]), $this->request->get['uid'], $ind), Hm_Request_Key::generate()); file_put_contents($file_path, $content); $new_attachment['tmp_name'] = $file_path; $new_attachment['filename'] = $file_path; @@ -126,14 +125,13 @@ public function process() { if (array_key_exists('forward_as_attachment', $this->request->get)) { $path = explode('_', $this->request->get['list_path']); - $imap = Hm_IMAP_List::connect($path[1]); - if ($imap && $imap->select_mailbox(hex2bin($path[2]))) { - $msg_struct = $imap->get_message_structure($this->request->get['uid']); - $msg_header = $imap->get_message_headers($this->request->get['uid']); + $mailbox = Hm_IMAP_List::get_connected_mailbox($path[1]); + if ($mailbox && $mailbox->authed()) { + $msg_header = $mailbox->get_message_headers(hex2bin($path[2]), $this->request->get['uid']); if (!array_key_exists('From', $msg_header) || count($msg_header) == 0) { return; } - $content = $imap->get_message_content($this->request->get['uid'], 0, false, false); + $content = $mailbox->get_message_content(hex2bin($path[2]), $this->request->get['uid']); # Attachment Download $attached_files = []; @@ -172,11 +170,10 @@ public function process() { } if (array_key_exists('forward', $this->request->get)) { $path = explode('_', $this->request->get['list_path']); - $imap = Hm_IMAP_List::connect($path[1]); - if ($imap && $imap->select_mailbox(hex2bin($path[2]))) { - $msg_struct = $imap->get_message_structure($this->request->get['uid']); - list($part, $msg_text) = $imap->get_first_message_part($this->request->get['uid'], 'text', 'plain', $msg_struct); - $msg_header = $imap->get_message_headers($this->request->get['uid']); + $mailbox = Hm_IMAP_List::get_connected_mailbox($path[1]); + if ($mailbox && $mailbox->authed()) { + list ($msg_struct, $msg_struct_first, $msg_text, $part) = $mailbox->get_structured_message(hex2bin($path[2]), $this->request->get['uid'], false, true); + $msg_header = $mailbox->get_message_headers(hex2bin($path[2]), $this->request->get['uid']); if (!array_key_exists('From', $msg_header) || count($msg_header) == 0) { return; } @@ -196,7 +193,7 @@ public function process() { $new_attachment['size'] = $sub['size']; $new_attachment['type'] = $sub['type']; $file_path = $file_dir . $new_attachment['name']; - $content = Hm_Crypt::ciphertext($imap->get_message_content($this->request->get['uid'], $ind), Hm_Request_Key::generate()); + $content = Hm_Crypt::ciphertext($mailbox->get_message_content(hex2bin($path[2]), $this->request->get['uid'], $ind), Hm_Request_Key::generate()); file_put_contents($file_path, $content); $new_attachment['tmp_name'] = $file_path; $new_attachment['filename'] = $file_path; @@ -804,13 +801,13 @@ public function process() { $msg_path = explode('_', $this->request->post['compose_msg_path']); $msg_uid = $this->request->post['compose_msg_uid']; - $imap = Hm_IMAP_List::connect($msg_path[1]); - if ($imap->select_mailbox(hex2bin($msg_path[2]))) { + $mailbox = Hm_IMAP_List::get_connected_mailbox($msg_path[1]); + if ($mailbox && $mailbox->authed()) { $specials = get_special_folders($this, $msg_path[1]); if (array_key_exists('archive', $specials) && $specials['archive']) { $archive_folder = $specials['archive']; - $imap->message_action('ARCHIVE', array($msg_uid)); - $imap->message_action('MOVE', array($msg_uid), $archive_folder); + $mailbox->message_action(hex2bin($msg_path[2]), 'ARCHIVE', array($msg_uid)); + $mailbox->message_action(hex2bin($msg_path[2]), 'MOVE', array($msg_uid), $archive_folder); } } } @@ -1757,13 +1754,10 @@ function get_primary_recipient($profiles, $headers, $smtp_servers, $is_draft=Fal */ if (!hm_exists('delete_draft')) { function delete_draft($id, $cache, $imap_server_id, $folder) { - $imap = Hm_IMAP_List::connect($imap_server_id); - if (! imap_authed($imap)) { - return false; - } - if ($imap->select_mailbox($folder)) { - if ($imap->message_action('DELETE', array($id))) { - $imap->message_action('EXPUNGE', array($id)); + $mailbox = Hm_IMAP_List::get_connected_mailbox($imap_server_id); + if ($mailbox && $mailbox->authed()) { + if ($mailbox->message_action($folder, 'DELETE', array($id))) { + $mailbox->message_action($folder, 'EXPUNGE', array($id)); return true; } } @@ -1919,9 +1913,10 @@ function save_imap_draft($atts, $id, $session, $mod, $mod_cache, $uploaded_files Hm_Msgs::add('ERRThere is no draft directory configured for this account.'); return -1; } - $cache = Hm_IMAP_List::get_cache($mod_cache, $imap_profile['id']); - $imap = Hm_IMAP_List::connect($imap_profile['id'], $cache); - $draft_folder = $imap->select_mailbox($specials['draft']); + $mailbox = Hm_IMAP_List::get_connected_mailbox($imap_profile['id'], $mod_cache); + if (! $mailbox || ! $mailbox->authed()) { + return -1; + } $mime = new Hm_MIME_Msg( $atts['draft_to'], @@ -1943,24 +1938,21 @@ function save_imap_draft($atts, $id, $session, $mod, $mod_cache, $uploaded_files $msg = str_replace("\n", "\r\n", $msg); $msg = rtrim($msg)."\r\n"; - if ($imap->append_start($specials['draft'], mb_strlen($msg), false, true)) { - $imap->append_feed($msg."\r\n"); - if (!$imap->append_end()) { - Hm_Msgs::add('ERRAn error occurred saving the draft message'); - return -1; - } + if (! $mailbox->store_message($specials['draft'], $msg, false, true)) { + Hm_Msgs::add('ERRAn error occurred saving the draft message'); + return -1; } - $mailbox_page = $imap->get_mailbox_page($specials['draft'], 'ARRIVAL', true, 'DRAFT', 0, 10); + $messages = $mailbox->get_messages($specials['draft'], 'ARRIVAL', true, 'DRAFT', 0, 10); // Remove old version from the mailbox if ($id) { - $imap->message_action('DELETE', array($id)); - $imap->message_action('EXPUNGE', array($id)); + $mailbox->message_action($specials['draft'], 'DELETE', array($id)); + $mailbox->message_action($specials['draft'], 'EXPUNGE', array($id)); } - foreach ($mailbox_page[1] as $mail) { - $msg_header = $imap->get_message_headers($mail['uid']); + foreach ($messages[1] as $mail) { + $msg_header = $mailbox->get_message_headers($specials['draft'], $mail['uid']); // Convert all header keys to lowercase $msg_header_lower = array_change_key_case($msg_header, CASE_LOWER); $mime_headers_lower = array_change_key_case($mime->get_headers(), CASE_LOWER); From 263665e90dd2aed700be27db758696277d007f89 Mon Sep 17 00:00:00 2001 From: Victor Emanouilov Date: Fri, 11 Oct 2024 16:45:20 +0300 Subject: [PATCH 3/7] UI updates - manage EWS servers like IMAP/SMTP ones, auth and start using the EWS API --- modules/core/output_modules.php | 2 +- modules/core/setup.php | 2 +- modules/core/site.js | 4 +- modules/imap/functions.php | 7 +- modules/imap/handler_modules.php | 48 +++++++++ modules/imap/hm-ews.php | 27 ++++- modules/imap/hm-mailbox.php | 2 +- modules/imap/output_modules.php | 165 +++++++++++++++++++++++++++++++ modules/imap/setup.php | 12 +++ modules/imap/site.js | 21 ++++ 10 files changed, 280 insertions(+), 10 deletions(-) diff --git a/modules/core/output_modules.php b/modules/core/output_modules.php index e763fe52b2..9df95923f5 100644 --- a/modules/core/output_modules.php +++ b/modules/core/output_modules.php @@ -2109,7 +2109,7 @@ protected function output() { $hasJmapActivated = in_array('jmap', $this->get('router_module_list'), true); if($hasImapActivated){ - $imap_servers_count = count(array_filter($this->get('imap_servers', array()), function($v) { return !array_key_exists('type', $v) || $v['type'] != 'jmap'; })); + $imap_servers_count = count(array_filter($this->get('imap_servers', array()), function($v) { return !array_key_exists('type', $v) || $v['type'] == 'imap'; })); $accordionTitle .= 'IMAP'; $configuredText .= ' ' . $imap_servers_count .' IMAP'; $hasEssentialModuleActivated = true; diff --git a/modules/core/setup.php b/modules/core/setup.php index 6e5003b6cc..879003f54c 100644 --- a/modules/core/setup.php +++ b/modules/core/setup.php @@ -336,6 +336,6 @@ 'srv_setup_stepper_only_jmap' => FILTER_VALIDATE_BOOLEAN, 'srv_setup_stepper_jmap_hide_from_c_page' => FILTER_VALIDATE_BOOLEAN, 'srv_setup_stepper_jmap_address' => FILTER_DEFAULT, - 'srv_setup_stepper_imap_hide_from_c_page' => FILTER_VALIDATE_BOOLEAN + 'srv_setup_stepper_imap_hide_from_c_page' => FILTER_VALIDATE_BOOLEAN, ) ); diff --git a/modules/core/site.js b/modules/core/site.js index 4f81a9bfc9..d720785ad5 100644 --- a/modules/core/site.js +++ b/modules/core/site.js @@ -2311,9 +2311,9 @@ function resetQuickSetupForm() { function handleCreateProfileCheckboxChange(checkbox) { if(checkbox.checked) { - $('#srv_setup_stepper_profile_bloc').show(); + $(checkbox).closest('.form-check').next().show(); }else{ - $('#srv_setup_stepper_profile_bloc').hide(); + $(checkbox).closest('.form-check').next().hide(); } } diff --git a/modules/imap/functions.php b/modules/imap/functions.php index c6812cf7f6..2f6121e5e1 100644 --- a/modules/imap/functions.php +++ b/modules/imap/functions.php @@ -1498,6 +1498,7 @@ function connect_to_imap_server($address, $name, $port, $user, $pass, $tls, $ima $imap_list = array( 'name' => $name, 'server' => $address, + 'type' => $type, 'hide' => $hidden, 'port' => $port, 'user' => $user, @@ -1508,8 +1509,6 @@ function connect_to_imap_server($address, $name, $port, $user, $pass, $tls, $ima } if ($type === 'jmap') { - $imap_list['type'] = 'jmap'; - $imap_list['hide'] = $hidden; $imap_list['port'] = false; $imap_list['tls'] = false; } @@ -1526,7 +1525,7 @@ function connect_to_imap_server($address, $name, $port, $user, $pass, $tls, $ima } } else { $imap_server_id = Hm_IMAP_List::add($imap_list); - if (! can_save_last_added_server('Hm_IMAP_List', $user)) { + if ($type != 'ews' && ! can_save_last_added_server('Hm_IMAP_List', $user)) { return; } } @@ -1556,7 +1555,7 @@ function connect_to_imap_server($address, $name, $port, $user, $pass, $tls, $ima if ($mailbox->authed()) { return $imap_server_id; - }else { + } else { Hm_IMAP_List::del($imap_server_id); Hm_Msgs::add('ERRAuthentication failed'); return null; diff --git a/modules/imap/handler_modules.php b/modules/imap/handler_modules.php index d69e80cdcf..dd9c153474 100644 --- a/modules/imap/handler_modules.php +++ b/modules/imap/handler_modules.php @@ -1402,6 +1402,54 @@ public function process() { } } +/** + * Save EWS server details + * @subpackage imap/handler + */ +class Hm_Handler_save_ews_server extends Hm_Handler_Module { + public function process() { + list($success, $form) = $this->process_form(array( + 'ews_profile_name', + 'ews_email', + 'ews_password', + 'ews_server', + 'ews_server_id', + 'ews_hide_from_c_page', + 'ews_create_profile', + 'ews_profile_signature', + 'ews_profile_reply_to', + 'ews_profile_is_default', + )); + // var_dump($success); + // var_dump($form); + // exit; + if ($success) { + $server_id = connect_to_imap_server( + $form['ews_server'], + $form['ews_profile_name'], + null, + $form['ews_email'], + $form['ews_password'], + null, + null, + null, + 'ews', + $this, + $form['ews_hide_from_c_page'], + $form['ews_server_id'], + ); + if(empty($server_id)) { + Hm_Msgs::add("ERRCould not save server"); + return; + }; + // TODO: EWS check if we shouldn't add the same server to smtp server list for the profile + if ($form['ews_create_profile']) { + add_profile($form['ews_profile_name'], $form['ews_profile_signature'], $form['ews_profile_reply_to'], $form['ews_profile_is_default'], $form['ews_email'], $form['ews_server'], $server_id, $server_id, $this); + } + } + } +} + /** * Save IMAP servers * @subpackage imap/handler diff --git a/modules/imap/hm-ews.php b/modules/imap/hm-ews.php index 71e74f41f3..b73ea49887 100644 --- a/modules/imap/hm-ews.php +++ b/modules/imap/hm-ews.php @@ -5,15 +5,40 @@ * @package modules * @subpackage imap * - * This is a drop-in replacment of IMAP and JMAP classes that allows usage of Exchange Web Services (EWS) + * This is a drop-in replacment of IMAP, JMAP and SMTP classes that allows usage of Exchange Web Services (EWS) * in all functions provided in imap and smtp modules - accessing mailbox, folders, reading messages, * attachments, moving, copying, read/unread, flags, sending messages. * Connection to EWS is handled by garethp/php-ews package handling NLTM auth and SOAP calls. */ +use garethp\ews\API\Enumeration; +use garethp\ews\API\Exception; +use garethp\ews\API\ExchangeWebServices; +use garethp\ews\API\Type; +use garethp\ews\MailAPI; + /** * public interface to EWS mailboxes * @subpackage imap/lib */ class Hm_EWS { + protected $ews; + protected $api; + protected $authed = false; + + public function connect(array $config) { + try { + $this->ews = ExchangeWebServices::fromUsernameAndPassword($config['server'], $config['username'], $config['password'], ['version' => ExchangeWebServices::VERSION_2016]); + $this->api = new MailAPI($this->ews); + $this->api->getFolderByDistinguishedId(Enumeration\DistinguishedFolderIdNameType::INBOX); + $this->authed = true; + return true; + } catch (Exception\UnauthorizedException $e) { + return false; + } + } + + public function authed() { + return $this->authed; + } } diff --git a/modules/imap/hm-mailbox.php b/modules/imap/hm-mailbox.php index 4bdadeb07e..240a935949 100644 --- a/modules/imap/hm-mailbox.php +++ b/modules/imap/hm-mailbox.php @@ -52,7 +52,7 @@ public function authed() { if ($this->is_imap()) { return $this->connection->get_state() == 'authenticated' || $this->connection->get_state() == 'selected'; } else { - // TODO: EWS + return $this->connection->authed(); } } diff --git a/modules/imap/output_modules.php b/modules/imap/output_modules.php index c1190f2481..8a45b323ae 100644 --- a/modules/imap/output_modules.php +++ b/modules/imap/output_modules.php @@ -455,6 +455,10 @@ protected function output() { $type = 'JMAP'; } + if (array_key_exists('type', $vals) && $vals['type'] == 'ews') { + continue; + } + if (array_key_exists('user', $vals) && !array_key_exists('nopass', $vals)) { $disabled = 'disabled="disabled"'; $user_pc = $vals['user']; @@ -1428,6 +1432,167 @@ protected function output() { } } +class Hm_Output_server_config_ews extends Hm_Output_Module { + protected function output() { + $hasEWSActivated = in_array('imap', $this->get('router_module_list'), true); + + if(! $hasEWSActivated){ + return ''; + } + + $ews_servers_count = count(array_filter($this->get('imap_servers', array()), function($v) { return array_key_exists('type', $v) && $v['type'] == 'ews'; })); + + $res = '
+
+ + + ' . $this->trans('Exchange Servers') . ' + +
' . $ews_servers_count .' ' . $this->trans('EWS') . '
+
+
+
+
+
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + +
+
+ + + +
+
+
+ + +
+
+ + +
+
+ + + +
+
+
+
+ +
+
+
+ '; + + $list = $this->get('imap_servers', array()); + foreach ($list as $index => $vals) { + if (! array_key_exists('type', $vals) || $vals['type'] != 'ews') { + continue; + } + + $server_id = $vals['id']; + + if (array_key_exists('user', $vals) && !array_key_exists('nopass', $vals)) { + $disabled = 'disabled="disabled"'; + $user_pc = $vals['user']; + $pass_pc = $this->trans('[saved]'); + $pass_value = '*************'; + } + elseif (array_key_exists('nopass', $vals)) { + if (array_key_exists('user', $vals)) { + $user_pc = $vals['user']; + } + else { + $user_pc = ''; + } + $pass_pc = $this->trans('Password'); + $disabled = ''; + $pass_value = ''; + } + else { + $user_pc = ''; + $pass_pc = $this->trans('Password'); + $disabled = ''; + $pass_value = ''; + } + $res .= '
'; + $res .= '
'; + $res .= ''; + $res .= ''; + $res .= '
'; + $res .= sprintf(' +
%s
+
%s
', + $this->html_safe($vals['name']), $this->html_safe($vals['server'])); + + $res .= '
'; + + $res .= '
'; + $res .= ''; + $res .= '
'; + $res .= '
'; + $res .= '
'; + $res .= ''; + $res .= '
'; + $res .= '
'; + + $res .= '
'; + + // Buttons + $disabled = isset($vals['default']) ? ' disabled': ''; + if (!isset($vals['user']) || !$vals['user']) { + $res .= ''; + $res .= ''; + } else { + $keysToRemove = array('object', 'connected', 'default', 'nopass'); + $serverDetails = array_diff_key($vals, array_flip($keysToRemove)); + + $res .= 'html_safe(json_encode($serverDetails)).'\' data-id="'.$this->html_safe($serverDetails['name']).'" data-type="ews" />'; + $res .= ''; + $res .= ''; + $res .= ''; + } + + // Hide/Unhide Buttons + $hidden = array_key_exists('hide', $vals) && $vals['hide']; + $res .= ''; + $res .= ''; + + $res .= ''; + $res .= '
'; + } + + $res .= ' +
+
'; + + return $res; + } +} + /** * Option to set the per page count for IMAP folder views * @subpackage imap/output diff --git a/modules/imap/setup.php b/modules/imap/setup.php index 5056817aba..ba368b15ed 100644 --- a/modules/imap/setup.php +++ b/modules/imap/setup.php @@ -22,12 +22,14 @@ add_handler('servers', 'process_add_imap_server', true, 'imap', 'message_list_type', 'after'); add_handler('servers', 'process_add_jmap_server', true, 'imap', 'process_add_imap_server', 'after'); add_handler('servers', 'save_imap_servers', true, 'imap', 'process_add_jmap_server', 'after'); +add_handler('servers', 'save_ews_server', true, 'imap', 'save_imap_servers', 'after'); add_output('servers', 'display_configured_imap_servers', true, 'imap', 'server_config_stepper_accordion_end_part', 'before'); add_output('servers', 'imap_server_ids', true, 'imap', 'page_js', 'before'); add_output('servers', 'stepper_setup_server_jmap', true, 'imap', 'server_config_stepper_end_part', 'before'); add_output('servers', 'stepper_setup_server_imap', true, 'imap', 'server_config_stepper_end_part', 'before'); add_output('servers', 'stepper_setup_server_jmap_imap_common', true, 'imap', 'server_config_stepper_end_part', 'before'); +add_output('servers', 'server_config_ews', true, 'imap', 'server_config_stepper_accordion_end_part', 'after'); /* settings page data */ add_handler('settings', 'process_sent_since_setting', true, 'imap', 'date', 'after'); @@ -437,5 +439,15 @@ 'tag_id' => FILTER_DEFAULT, 'first_time_screen_emails' => FILTER_VALIDATE_INT, 'move_messages_in_screen_email' => FILTER_VALIDATE_BOOLEAN, + 'ews_server_id' => FILTER_DEFAULT, + 'ews_profile_name' => FILTER_DEFAULT, + 'ews_email' => FILTER_DEFAULT, + 'ews_password' => FILTER_UNSAFE_RAW, + 'ews_server' => FILTER_DEFAULT, + 'ews_hide_from_c_page' => FILTER_VALIDATE_INT, + 'ews_create_profile' => FILTER_VALIDATE_INT, + 'ews_profile_is_default' => FILTER_VALIDATE_INT, + 'ews_profile_signature' => FILTER_DEFAULT, + 'ews_profile_reply_to' => FILTER_DEFAULT, ) ); diff --git a/modules/imap/site.js b/modules/imap/site.js index 5017f139dc..42fc8c4d66 100644 --- a/modules/imap/site.js +++ b/modules/imap/site.js @@ -121,6 +121,7 @@ var imap_setup_server_page = function() { $('.unhide_imap_connection').on('click', imap_unhide); $('.forget_imap_connection').on('click', imap_forget_action); $('.test_imap_connect').on('click', imap_test_action); + $('.edit_ews_server_connection').on('click', ews_edit_action); var dsp = Hm_Utils.get_from_local_storage('.imap_section'); if (dsp === 'block' || dsp === 'none') { @@ -130,6 +131,26 @@ var imap_setup_server_page = function() { if (jdsp === 'block' || jdsp === 'none') { $('.jmap_section').css('display', jdsp); } + + $('.ews-btn').on('click', function() { + $(this).hide().prev().removeClass('d-none'); + }); +}; + +var ews_edit_action = function(event) { + event.preventDefault(); + Hm_Notices.hide(true); + var details = $(this).data('server-details'); + + $('.ews-btn').trigger('click'); + $('#ews_profile_name').val(details.name).trigger('focus'); + $('#ews_email').val(details.user); + $('#ews_password').val(''); + $('#ews_profile_reply_to').val(''); + $('#ews_create_profile').trigger("click", true); + $('#ews_server').val(details.server); + $('#ews_server_id').val(details.id); + $('#ews_hide_from_c_page').prop("checked", details.hide); }; var set_message_content = function(path, msg_uid) { From 8ed434cd0496aea339a06cc0f6031042d55bf150 Mon Sep 17 00:00:00 2001 From: Victor Emanouilov Date: Mon, 14 Oct 2024 19:44:47 +0300 Subject: [PATCH 4/7] EWS: folder management and message display --- modules/imap/functions.php | 8 +- modules/imap/hm-ews.php | 151 +++++++++++++++++++++++++++++++ modules/imap/hm-mailbox.php | 48 ++++++---- modules/imap_folders/modules.php | 20 ++-- 4 files changed, 195 insertions(+), 32 deletions(-) diff --git a/modules/imap/functions.php b/modules/imap/functions.php index 2f6121e5e1..e518afc6a5 100644 --- a/modules/imap/functions.php +++ b/modules/imap/functions.php @@ -1208,14 +1208,14 @@ function decode_folder_str($folder) { * @subpackage imap/functions */ if (!hm_exists('prep_folder_name')) { -function prep_folder_name($mailbox, $folder, $decode_folder=false, $parent=false) { +function prep_folder_name($imap, $folder, $decode_folder=false, $parent=false) { if ($parent && $decode_folder) { $parent = decode_folder_str($parent); } if ($decode_folder) { $folder = decode_folder_str($folder); } - $ns = get_personal_ns($mailbox); + $ns = get_personal_ns($imap); if (!$folder) { return false; } @@ -1235,8 +1235,8 @@ function prep_folder_name($mailbox, $folder, $decode_folder=false, $parent=false * @subpackage imap/functions */ if (!hm_exists('get_personal_ns')) { -function get_personal_ns($mailbox) { - $namespaces = $mailbox->get_namespaces(); +function get_personal_ns($imap) { + $namespaces = $imap->get_namespaces(); foreach ($namespaces as $ns) { if ($ns['class'] == 'personal') { return $ns; diff --git a/modules/imap/hm-ews.php b/modules/imap/hm-ews.php index b73ea49887..540c8ccf7b 100644 --- a/modules/imap/hm-ews.php +++ b/modules/imap/hm-ews.php @@ -41,4 +41,155 @@ public function connect(array $config) { public function authed() { return $this->authed; } + + public function get_folders($folder = null) { + $result = []; + if (empty($folder)) { + $folder = new Type\DistinguishedFolderIdType(Enumeration\DistinguishedFolderIdNameType::MESSAGE_ROOT); + } else { + $folder = new Type\FolderIdType($folder); + } + $request = array( + 'Traversal' => 'Deep', + 'FolderShape' => array( + 'BaseShape' => 'AllProperties', + ), + 'ParentFolderIds' => $folder->toArray(true) + ); + $resp = $this->ews->FindFolder($request); + $folders = $resp->get('folders')->get('folder'); + if ($folders) { + foreach($folders as $folder) { + $id = $folder->get('folderId')->get('id'); + $name = $folder->get('displayName'); + $result[$id] = array( + 'id' => $id, + 'parent' => null, // TODO + 'delim' => false, // TODO - check, might be IMAP-specific + 'name' => $name, + 'name_parts' => [], // TODO - check, might be IMAP-specific + 'basename' => $name, + 'realname' => $name, + 'namespace' => '', // TODO - check, might be IMAP-specific + // TODO - flags + 'marked' => false, + 'noselect' => false, + 'can_have_kids' => true, + 'has_kids' => $folder->get('childFolderCount') > 0, + 'special' => true, + 'clickable' => true + ); + } + } + return $result; + } + + public function get_folder_status($folder) { + try { + $result = $this->api->getFolder((new Type\FolderIdType($folder))->toArray(true)); + return [ + 'messages' => $result->get('totalCount'), + 'uidvalidity' => false, + 'uidnext' => false, + 'recent' => false, + 'unseen' => $result->get('unreadCount'), + ]; + } catch (Exception $e) { + return []; + } + } + + public function create_folder($folder, $parent = null) { + if (empty($parent)) { + $parent = Enumeration\DistinguishedFolderIdNameType::MESSAGE_ROOT; + } + try { + return $this->api->createFolders([$folder], new Type\DistinguishedFolderIdType($parent)); + } catch(Exception $e) { + Hm_Msgs::add('ERR' . $e->getMessage()); + return false; + } + } + + public function rename_folder($folder, $new_name, $parent = null) { + $result = []; + $new_folder = new Type\FolderType(); + $new_folder->displayName = $new_name; + if ($parent) { + $new_folder->parentFolderId = new Type\FolderIdType($parent); + } + $setFolderField = new Type\SetFolderFieldType(); + $setFolderField->folder = $new_folder; + $fieldURI = new Type\FieldURI(); + $fieldURI->fieldURI = 'folder:displayName'; + $setFolderField->fieldURI = $fieldURI; + $updates = new Type\NonEmptyArrayOfFolderChangeDescriptionsType(); + $updates->set('setFolderField', [$setFolderField]); + $change = new Type\FolderChangeType(); + $change->folderId = new Type\FolderIdType($folder); + $change->updates = $updates; + $request = [ + 'FolderChanges' => [ + $change + ], + ]; + try { + $resp = $this->ews->UpdateFolder($request); + // TODO: EWS: resolve internal server error issue and return status + return true; + } catch (Exception $e) { + Hm_Msgs::add('ERR' . $e->getMessage()); + return false; + } + } + + public function delete_folder($folder) { + try { + return $this->api->deleteFolder(new Type\FolderIdType($folder)); + } catch(Exception $e) { + Hm_Msgs::add('ERR' . $e->getMessage()); + return false; + } + } + + public function get_messages($folder, $sort, $reverse, $flag_filter, $offset, $limit, $keyword, $trusted_senders) { + $folder = new Type\FolderIdType($folder); + $result = $this->api->getMailItems($folder, [ + // TODO: sort, pagination, search + ]); + $messages = []; + foreach ($result->get('items')->get('message') as $message) { + $flags = []; + if ($message->get('isRead')) { + $flags[] = '\\Seen'; + } + if ($message->get('isDraft')) { + $flags[] = '\\Draft'; + } + // TODO: EWS - check \Answered, \Flagged, \Deleted flags + $messages[] = [ + 'uid' => $message->get('itemId')->get('id'), + 'flags' => implode(' ', $flags), + 'internal_date' => $message->get('dateTimeCreated'), + 'size' => $message->get('size'), + 'date' => $message->get('dateTimeReceived'), + 'from' => $message->get('from')->get('mailbox')->get('emailAddress'), + 'to' => $message->get('receivedBy')->get('mailbox')->get('emailAddress'), + 'subject' => $message->get('subject'), + 'content-type' => $message->get('mimeContent'), + 'timestamp' => time(), + 'charset' => null, + 'x-priority' => null, + 'google_msg_id' => null, + 'google_thread_id' => null, + 'google_labels' => null, + 'list_archive' => null, + 'references' => $message->get('references'), + 'message_id' => $message->get('internetMessageId'), + 'x_auto_bcc' => null, + 'x_snoozed' => null, + ]; + } + return [$result->get('totalItemsInView'), $messages]; + } } diff --git a/modules/imap/hm-mailbox.php b/modules/imap/hm-mailbox.php index 240a935949..0048930499 100644 --- a/modules/imap/hm-mailbox.php +++ b/modules/imap/hm-mailbox.php @@ -33,6 +33,10 @@ public function connect(array $config) { return $this->connection->connect($config); } + public function get_connection() { + return $this->connection; + } + public function is_imap() { return $this->type !== self::TYPE_EWS; } @@ -63,29 +67,33 @@ public function get_folder_status($folder) { if ($this->is_imap()) { return $this->connection->get_mailbox_status($folder); } else { - // TODO: EWS + return $this->connection->get_folder_status($folder); } } - public function create_folder($folder) { + public function create_folder($folder, $parent = null) { if (! $this->authed()) { return; } if ($this->is_imap()) { - return $this->connection->create_mailbox($folder); + $new_folder = prep_folder_name($this->connection, $folder, false, $parent); + return $this->connection->create_mailbox($new_folder); } else { - // TODO: EWS + return $this->connection->create_folder($folder, $parent); } } - public function rename_folder($folder, $new_name) { + public function rename_folder($folder, $new_name, $parent = null) { if (! $this->authed()) { return; } if ($this->is_imap()) { - return $this->connection->rename_mailbox($folder, $new_name); + $old_folder = prep_folder_name($this->connection, $folder, true); + $new_folder = prep_folder_name($this->connection, $new_name, false, $parent); + return $this->connection->rename_mailbox($old_folder, $new_folder); } else { - // TODO: EWS + $folder = decode_folder_str($folder); + return $this->connection->rename_folder($folder, $new_name, $parent); } } @@ -94,9 +102,19 @@ public function delete_folder($folder) { return; } if ($this->is_imap()) { - return $this->connection->delete_mailbox($folder); + $del_folder = prep_folder_name($this->connection, $folder, true); + return $this->connection->delete_mailbox($del_folder); } else { - // TODO: EWS + $del_folder = decode_folder_str($folder); + return $this->connection->delete_folder($del_folder); + } + } + + public function prep_folder_name($folder) { + if ($this->is_imap()) { + return prep_folder_name($this->connection, $folder, true); + } else { + return $folder; } } @@ -118,7 +136,8 @@ public function get_folders($only_subscribed = false) { if ($this->is_imap()) { return $this->connection->get_mailbox_list($only_subscribed); } else { - // TODO: EWS + // TODO: EWS only_subscribed + return $this->connection->get_folders(); } } @@ -129,7 +148,8 @@ public function get_subfolders($folder, $only_subscribed = false, $with_input = if ($this->is_imap()) { return $this->connection->get_folder_list_by_level($folder, $only_subscribed, $with_input); } else { - // TODO: EWS + // TODO: EWS only_subscribed and with_input + return $this->connection->get_folders($folder); } } @@ -169,7 +189,7 @@ public function get_messages($folder, $sort, $reverse, $flag_filter, $offset=0, if ($this->is_imap()) { return $this->connection->get_mailbox_page($folder, $sort, $reverse, $flag_filter, $offset, $limit, $keyword, $trusted_senders); } else { - // TODO: EWS + return $this->connection->get_messages($folder, $sort, $reverse, $flag_filter, $offset, $limit, $keyword, $trusted_senders); } } @@ -414,10 +434,6 @@ public function get_capability() { return $this->connection->get_capability(); } - public function get_namespaces() { - return $this->connection->get_namespaces(); - } - public function set_read_only($read_only) { if ($this->is_imap()) { $this->connection->read_only = $read_only; diff --git a/modules/imap_folders/modules.php b/modules/imap_folders/modules.php index 275ecc1779..abb23699f6 100644 --- a/modules/imap_folders/modules.php +++ b/modules/imap_folders/modules.php @@ -103,7 +103,7 @@ public function process() { Hm_Msgs::add('ERRUnable to connect to the selected IMAP server'); return; } - $new_folder = prep_folder_name($mailbox, $form['folder'], true); + $new_folder = $mailbox->prep_folder_name($form['folder']); if (! $new_folder || ! $mailbox->get_folder_status($new_folder)) { Hm_Msgs::add('ERRSelected folder not found'); return; @@ -174,14 +174,12 @@ public function process() { list($success, $form) = $this->process_form(array('folder', 'imap_server_id')); if ($success) { $parent = false; - $parent_str = false; if (array_key_exists('parent', $this->request->post) && trim($this->request->post['parent'])) { - $parent_str = decode_folder_str($this->request->post['parent']); + $parent = decode_folder_str($this->request->post['parent']); } $mailbox = Hm_IMAP_List::get_connected_mailbox($form['imap_server_id'], $this->cache); if ($mailbox && $mailbox->authed()) { - $new_folder = prep_folder_name($mailbox, $form['folder'], false, $parent_str); - if ($new_folder && $mailbox->create_folder($new_folder)) { + if ($form['folder'] && $mailbox->create_folder($form['folder'], $parent)) { Hm_Msgs::add('Folder created'); $this->cache->del('imap_folders_imap_'.$form['imap_server_id'].'_'); $this->out('imap_folders_success', true); @@ -201,15 +199,13 @@ class Hm_Handler_process_folder_rename extends Hm_Handler_Module { public function process() { list($success, $form) = $this->process_form(array('imap_server_id', 'folder', 'new_folder')); if ($success) { - $parent_str = false; + $parent = false; if (array_key_exists('parent', $this->request->post)) { - $parent_str = $this->request->post['parent']; + $parent = $this->request->post['parent']; } $mailbox = Hm_IMAP_List::get_connected_mailbox($form['imap_server_id'], $this->cache); if ($mailbox && $mailbox->authed()) { - $old_folder = prep_folder_name($mailbox, $form['folder'], true); - $new_folder = prep_folder_name($mailbox, $form['new_folder'], false, $parent_str); - if ($new_folder && $old_folder && $mailbox->rename_mailbox($old_folder, $new_folder)) { + if ($form['folder'] && $form['new_folder'] && $mailbox->rename_folder($form['folder'], $form['new_folder'], $parent)) { if ($this->module_is_supported('sievefilters') && $this->user_config->get('enable_sieve_filter_setting', DEFAULT_ENABLE_SIEVE_FILTER)) { $imap_servers = $this->user_config->get('imap_servers'); $imap_account = $imap_servers[$form['imap_server_id']]; @@ -267,14 +263,14 @@ public function process() { if ($success) { $mailbox = Hm_IMAP_List::get_connected_mailbox($form['imap_server_id'], $this->cache); if ($mailbox && $mailbox->authed()) { - $del_folder = prep_folder_name($mailbox, $form['folder'], true); if ($this->module_is_supported('sievefilters') && $this->user_config->get('enable_sieve_filter_setting', DEFAULT_ENABLE_SIEVE_FILTER)) { + $del_folder = prep_folder_name($mailbox->get_connection(), $form['folder'], true); if (is_mailbox_linked_with_filters($del_folder, $form['imap_server_id'], $this)) { Hm_Msgs::add('ERRThis folder can\'t be deleted because it is used in a filter.'); return; } } - if ($del_folder && $mailbox->delete_folder($del_folder)) { + if ($form['folder'] && $mailbox->delete_folder($form['folder'])) { Hm_Msgs::add('Folder deleted'); $this->cache->del('imap_folders_imap_'.$form['imap_server_id'].'_'); $this->out('imap_folders_success', true); From a42e9c527143a8fbe3d864043006e86b61a8de14 Mon Sep 17 00:00:00 2001 From: Victor Emanouilov Date: Wed, 16 Oct 2024 16:11:56 +0300 Subject: [PATCH 5/7] EWS: use a combination of FindItem and GetItem to get message headers appropriately for message list page --- modules/imap/hm-ews.php | 64 ++++++++++++++++++++++++++++++------- modules/imap/hm-mailbox.php | 7 +++- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/modules/imap/hm-ews.php b/modules/imap/hm-ews.php index 540c8ccf7b..ca9870888e 100644 --- a/modules/imap/hm-ews.php +++ b/modules/imap/hm-ews.php @@ -154,11 +154,38 @@ public function delete_folder($folder) { public function get_messages($folder, $sort, $reverse, $flag_filter, $offset, $limit, $keyword, $trusted_senders) { $folder = new Type\FolderIdType($folder); - $result = $this->api->getMailItems($folder, [ - // TODO: sort, pagination, search - ]); + $request = array( + 'Traversal' => 'Shallow', + 'ItemShape' => array( + 'BaseShape' => 'IdOnly' + ), + 'ParentFolderIds' => $folder->toArray(true) + ); + // TODO: sort, pagination, search + $request = Type::buildFromArray($request); + $result = $this->ews->FindItem($request); + $itemIds = array_map(function($msg) { + return $msg->get('itemId')->get('id'); + }, $result->get('items')->get('message')); + return [$result->get('totalItemsInView'), $this->get_message_list($itemIds)]; + } + + public function get_message_list($itemIds) { + $request = array( + 'ItemShape' => array( + 'BaseShape' => 'AllProperties' + ), + 'ItemIds' => [ + 'ItemId' => array_map(function($id) { + return ['Id' => $id]; + }, $itemIds), + ], + ); + $request = Type::buildFromArray($request); + $result = $this->ews->GetItem($request); $messages = []; - foreach ($result->get('items')->get('message') as $message) { + foreach ($result as $message) { + // TODO: EWS - check \Answered, \Flagged, \Deleted flags $flags = []; if ($message->get('isRead')) { $flags[] = '\\Seen'; @@ -166,17 +193,17 @@ public function get_messages($folder, $sort, $reverse, $flag_filter, $offset, $l if ($message->get('isDraft')) { $flags[] = '\\Draft'; } - // TODO: EWS - check \Answered, \Flagged, \Deleted flags - $messages[] = [ - 'uid' => $message->get('itemId')->get('id'), + $uid = bin2hex($message->get('itemId')->get('id')); + $msg = [ + 'uid' => $uid, 'flags' => implode(' ', $flags), 'internal_date' => $message->get('dateTimeCreated'), 'size' => $message->get('size'), 'date' => $message->get('dateTimeReceived'), - 'from' => $message->get('from')->get('mailbox')->get('emailAddress'), - 'to' => $message->get('receivedBy')->get('mailbox')->get('emailAddress'), + 'from' => $message->get('sender')->get('mailbox')->get('name') . ' <' . $message->get('from')->get('mailbox')->get('emailAddress') . '>', + 'to' => $message->get('toRecipients')->Mailbox->get('name') . ' <' . $message->get('toRecipients')->Mailbox->get('emailAddress') . '>', 'subject' => $message->get('subject'), - 'content-type' => $message->get('mimeContent'), + 'content-type' => null, 'timestamp' => time(), 'charset' => null, 'x-priority' => null, @@ -189,7 +216,22 @@ public function get_messages($folder, $sort, $reverse, $flag_filter, $offset, $l 'x_auto_bcc' => null, 'x_snoozed' => null, ]; + foreach ($message->get('internetMessageHeaders')->InternetMessageHeader as $header) { + foreach (['x-gm-msgid' => 'google_msg_id', 'x-gm-thrid' => 'google_thread_id', 'x-gm-labels' => 'google_labels', 'x-auto-bcc' => 'x_auto_bcc', 'message-id' => 'message_id', 'references' => 'references', 'x-snoozed' => 'x_snoozed', 'list-archive' => 'list_archive', 'content-type' => 'content-type', 'x-priority' => 'x-priority'] as $hname => $key) { + if (strtolower($header->get('headerName')) == $hname) { + $msg[$key] = (string) $header; + } + } + } + $cset = ''; + if (mb_stristr($msg['content-type'], 'charset=')) { + if (preg_match("/charset\=([^\s;]+)/", $msg['content-type'], $matches)) { + $cset = trim(mb_strtolower(str_replace(array('"', "'"), '', $matches[1]))); + } + } + $msg['charset'] = $cset; + $messages[$uid] = $msg; } - return [$result->get('totalItemsInView'), $messages]; + return $messages; } } diff --git a/modules/imap/hm-mailbox.php b/modules/imap/hm-mailbox.php index 0048930499..8dbf118e11 100644 --- a/modules/imap/hm-mailbox.php +++ b/modules/imap/hm-mailbox.php @@ -16,6 +16,7 @@ class Hm_Mailbox { protected $type; protected $connection; + protected $selected_folder; public function connect(array $config) { if (array_key_exists('type', $config) && $config['type'] == 'jmap') { @@ -166,7 +167,7 @@ public function get_selected_folder() { if ($this->is_imap()) { return $this->connection->selected_mailbox; } else { - // TODO: EWS + return $this->selected_folder; } } @@ -186,6 +187,9 @@ public function get_special_use_mailboxes($folder = false) { * @return array - [total results found, results for a single page] */ public function get_messages($folder, $sort, $reverse, $flag_filter, $offset=0, $limit=0, $keyword=false, $trusted_senders=[]) { + if (! $this->select_folder($folder)) { + return; + } if ($this->is_imap()) { return $this->connection->get_mailbox_page($folder, $sort, $reverse, $flag_filter, $offset, $limit, $keyword, $trusted_senders); } else { @@ -469,6 +473,7 @@ public function get_message_list($folder, $msg_ids) { } protected function select_folder($folder) { + $this->selected_folder = ['name' => $folder, 'detail' => []]; if ($this->is_imap()) { if (isset($this->connection->selected_mailbox['name']) && $this->connection->selected_mailbox['name'] == $folder) { return true; From ac7e7b98871808f5d8fff2b7d121f29ed3f5d067 Mon Sep 17 00:00:00 2001 From: Victor Emanouilov Date: Wed, 16 Oct 2024 17:36:20 +0300 Subject: [PATCH 6/7] EWS: handle display of folder names --- modules/imap/handler_modules.php | 9 +++++++-- modules/imap/hm-ews.php | 1 + modules/imap/hm-mailbox.php | 22 +++++++++++++++++++--- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/modules/imap/handler_modules.php b/modules/imap/handler_modules.php index dd9c153474..eb2345d302 100644 --- a/modules/imap/handler_modules.php +++ b/modules/imap/handler_modules.php @@ -573,7 +573,13 @@ public function process() { else { $folder = hex2bin($parts[2]); } - $title = array('IMAP', $details['name'], $folder); + $mailbox = Hm_IMAP_List::get_connected_mailbox($details['id'], $this->cache); + if ($mailbox && $mailbox->authed()) { + $label = $mailbox->get_folder_name($folder); + } else { + $label = $folder; + } + $title = array(strtoupper($details['type'] ?? 'IMAP'), $details['name'], $label); if ($this->get('list_page', 0)) { $title[] = sprintf('Page %d', $this->get('list_page', 0)); } @@ -751,7 +757,6 @@ public function process() { foreach ($results as $msg) { $msg['server_id'] = $form['imap_server_id']; $msg['server_name'] = $details['name']; - $msg['folder'] = $form['folder']; $msgs[] = $msg; } if ($folder = $mailbox->get_selected_folder()) { diff --git a/modules/imap/hm-ews.php b/modules/imap/hm-ews.php index ca9870888e..ad36cf309e 100644 --- a/modules/imap/hm-ews.php +++ b/modules/imap/hm-ews.php @@ -88,6 +88,7 @@ public function get_folder_status($folder) { try { $result = $this->api->getFolder((new Type\FolderIdType($folder))->toArray(true)); return [ + 'name' => $result->get('displayName'), 'messages' => $result->get('totalCount'), 'uidvalidity' => false, 'uidnext' => false, diff --git a/modules/imap/hm-mailbox.php b/modules/imap/hm-mailbox.php index 8dbf118e11..35f6beded6 100644 --- a/modules/imap/hm-mailbox.php +++ b/modules/imap/hm-mailbox.php @@ -72,6 +72,15 @@ public function get_folder_status($folder) { } } + public function get_folder_name($folder) { + if ($this->is_imap()) { + return $folder; + } else { + $result = $this->connection->get_folder_status($folder); + return $result['name']; + } + } + public function create_folder($folder, $parent = null) { if (! $this->authed()) { return; @@ -191,10 +200,15 @@ public function get_messages($folder, $sort, $reverse, $flag_filter, $offset=0, return; } if ($this->is_imap()) { - return $this->connection->get_mailbox_page($folder, $sort, $reverse, $flag_filter, $offset, $limit, $keyword, $trusted_senders); + $messages = $this->connection->get_mailbox_page($folder, $sort, $reverse, $flag_filter, $offset, $limit, $keyword, $trusted_senders); } else { - return $this->connection->get_messages($folder, $sort, $reverse, $flag_filter, $offset, $limit, $keyword, $trusted_senders); + $messages = $this->connection->get_messages($folder, $sort, $reverse, $flag_filter, $offset, $limit, $keyword, $trusted_senders); + $folder = $this->selected_folder['name']; } + foreach ($messages[1] as &$msg) { + $msg['folder'] = bin2hex($folder); + } + return $messages; } public function get_message_headers($folder, $msg_id) { @@ -473,7 +487,6 @@ public function get_message_list($folder, $msg_ids) { } protected function select_folder($folder) { - $this->selected_folder = ['name' => $folder, 'detail' => []]; if ($this->is_imap()) { if (isset($this->connection->selected_mailbox['name']) && $this->connection->selected_mailbox['name'] == $folder) { return true; @@ -481,6 +494,9 @@ protected function select_folder($folder) { if (! $this->connection->select_mailbox($folder)) { return false; } + } else { + $result = $this->get_folder_status($folder); + $this->selected_folder = ['id' => $folder, 'name' => $result['name'], 'detail' => []]; } return true; } From 0f7094ca05c9f97f0156a3175b6539567968083e Mon Sep 17 00:00:00 2001 From: Victor Emanouilov Date: Thu, 17 Oct 2024 20:25:01 +0300 Subject: [PATCH 7/7] EWS: get message structure similar to imap using mime content and parts, get message or part contents, display message --- modules/imap/hm-ews.php | 197 +++++++++++++++++++++++++++++++++++- modules/imap/hm-mailbox.php | 8 +- 2 files changed, 200 insertions(+), 5 deletions(-) diff --git a/modules/imap/hm-ews.php b/modules/imap/hm-ews.php index ad36cf309e..01e8c97d12 100644 --- a/modules/imap/hm-ews.php +++ b/modules/imap/hm-ews.php @@ -17,6 +17,8 @@ use garethp\ews\API\Type; use garethp\ews\MailAPI; +use ZBateson\MailMimeParser\MailMimeParser; + /** * public interface to EWS mailboxes * @subpackage imap/lib @@ -202,7 +204,7 @@ public function get_message_list($itemIds) { 'size' => $message->get('size'), 'date' => $message->get('dateTimeReceived'), 'from' => $message->get('sender')->get('mailbox')->get('name') . ' <' . $message->get('from')->get('mailbox')->get('emailAddress') . '>', - 'to' => $message->get('toRecipients')->Mailbox->get('name') . ' <' . $message->get('toRecipients')->Mailbox->get('emailAddress') . '>', + 'to' => $this->extract_mailbox($message->get('toRecipients')), 'subject' => $message->get('subject'), 'content-type' => null, 'timestamp' => time(), @@ -235,4 +237,197 @@ public function get_message_list($itemIds) { } return $messages; } + + public function get_message_headers($itemId) { + $request = array( + 'ItemShape' => array( + 'BaseShape' => 'AllProperties', + ), + 'ItemIds' => [ + 'ItemId' => ['Id' => hex2bin($itemId)], + ], + ); + $request = Type::buildFromArray($request); + $message = $this->ews->GetItem($request); + $headers = []; + $headers['Arrival Date'] = $message->get('dateTimeCreated'); + $headers['From'] = $message->get('sender')->get('mailbox')->get('name') . ' <' . $message->get('from')->get('mailbox')->get('emailAddress') . '>'; + $headers['To'] = $this->extract_mailbox($message->get('toRecipients')); + if ($message->get('ccRecipients')) { + $headers['Cc'] = $this->extract_mailbox($message->get('ccRecipients')); + } + if ($message->get('bccRecipients')) { + $headers['Bcc'] = $this->extract_mailbox($message->get('bccRecipients')); + } + foreach ($message->get('internetMessageHeaders')->InternetMessageHeader as $header) { + $name = $header->get('headerName'); + if (isset($headers[$name])) { + if (! is_array($headers[$name])) { + $headers[$name] = [$headers[$name]]; + } + $headers[$name][] = (string) $header; + } else { + $headers[$name] = (string) $header; + } + } + return $headers; + } + + public function get_message_content($itemId, $part) { + if ($part) { + list($msg_struct, $msg_struct_current, $msg_text, $part) = $this->get_structured_message($itemId, $part, false); + return $msg_text; + } else { + $message = $this->get_mime_message_by_id($itemId); + $content = $message->getHtmlContent(); + if (empty($content)) { + $content = $message->getTextContent(); + } + return $content; + } + } + + public function get_structured_message($itemId, $part, $text_only) { + $message = $this->get_mime_message_by_id($itemId); + $msg_struct = []; + $this->parse_mime_part($message, $msg_struct, 0); + if ($part !== false) { + $struct = $this->search_mime_part_in_struct($msg_struct, ['part_id' => $part], true); + } else { + $struct = null; + if (! $text_only) { + $struct = $this->search_mime_part_in_struct($msg_struct, ['type' => 'text', 'subtype' => 'html']); + } + if (! $struct) { + $struct = $this->search_mime_part_in_struct($msg_struct, ['type' => 'text']); + } + } + if ($struct) { + $part = array_key_first($struct); + $msg_struct_current = $struct[$part]; + $msg_text = $msg_struct_current['mime_object']->getContent(); + } else { + $part = false; + $msg_struct_current = null; + $msg_text = ''; + } + if (isset($msg_struct_current['subtype']) && mb_strtolower($msg_struct_current['subtype'] == 'html')) { + // add inline images + if (preg_match_all("/src=('|\"|)cid:([^\s'\"]+)/", $msg_text, $matches)) { + $cids = array_pop($matches); + foreach ($cids as $id) { + $struct = $this->search_mime_part_in_struct($msg_struct, ['id' => $id, 'type' => 'image']); + if ($struct) { + $struct = array_shift($struct); + $msg_text = str_replace('cid:'.$id, 'data:image/'.$struct['subtype'].';base64,'.base64_encode($struct['mime_object']->getContent()), $msg_text); + } + } + } + } + return [$msg_struct, $msg_struct_current, $msg_text, $part]; + } + + protected function parse_mime_part($part, &$struct, $part_num) { + $struct[$part_num] = []; + list($struct[$part_num]['type'], $struct[$part_num]['subtype']) = explode('/', $part->getContentType()); + if ($part->isMultiPart()) { + $boundary = $part->getHeaderParameter('Content-Type', 'boundary'); + if ($boundary) { + $struct[$part_num]['attributes'] = ['boundary' => $boundary]; + } + $struct[$part_num]['disposition'] = $part->getContentDisposition(); + $struct[$part_num]['language'] = ''; + $struct[$part_num]['location'] = ''; + } else { + $content = $part->getContent(); + $charset = $part->getCharset(); + if ($charset) { + $struct[$part_num]['attributes'] = ['charset' => $charset]; + } + $struct[$part_num]['id'] = $part->getContentId(); + $struct[$part_num]['description'] = $part->getHeaderValue('Content-Description'); + $struct[$part_num]['encoding'] = $part->getContentTransferEncoding(); + $struct[$part_num]['size'] = strlen($content); + $struct[$part_num]['lines'] = substr_count($content, "\n"); + $struct[$part_num]['md5'] = ''; + $struct[$part_num]['disposition'] = $part->getContentDisposition(); + $struct[$part_num]['file_attributes'] = ''; + $struct[$part_num]['language'] = ''; + $struct[$part_num]['location'] = ''; + } + $struct[$part_num]['mime_object'] = $part; + if ($part->getChildCount() > 0) { + $struct[$part_num]['subs'] = []; + foreach ($part->getChildParts() as $i => $child) { + $this->parse_mime_part($child, $struct[$part_num]['subs'], $part_num . '.' . ($i+1)); + } + } + } + + protected function search_mime_part_in_struct($struct, $conditions, $all = false) { + $found = []; + foreach ($struct as $part_id => $sub) { + $matches = 0; + if (isset($conditions['part_id']) && $part_id == $conditions['part_id']) { + $matches++; + } + foreach ($conditions as $name => $value) { + if (isset($sub[$name]) && mb_stristr($sub[$name], $value)) { + $matches++; + } + } + if ($matches === count($conditions)) { + $part = $sub; + if (isset($part['subs'])) { + $part['subs'] = count($part['subs']); + } + $found[$part_id] = $part; + if (! $all) { + break; + } + } + if (isset($sub['subs'])) { + $found = array_merge($found, $this->search_mime_part_in_struct($sub['subs'], $conditions, $all)); + } + if (! $all && $found) { + break; + } + } + return $found; + } + + protected function get_mime_message_by_id($itemId) { + $request = array( + 'ItemShape' => array( + 'BaseShape' => 'IdOnly', + 'IncludeMimeContent' => true, + ), + 'ItemIds' => [ + 'ItemId' => ['Id' => hex2bin($itemId)], + ], + ); + $request = Type::buildFromArray($request); + $message = $this->ews->GetItem($request); + $mime = $message->get('mimeContent'); + $content = base64_decode($mime); + if (strtoupper($mime->get('characterSet')) != 'UTF-8') { + $content = mb_convert_encoding($content, 'UTF-8', $mime->get('characterSet')); + } + $parser = new MailMimeParser(); + return $parser->parse($content, false); + } + + protected function extract_mailbox($data) { + if (is_array($data)) { + $result = []; + foreach ($data as $mailbox) { + $result[] = $this->extract_mailbox($mailbox); + } + return $result; + } elseif (is_object($data)) { + return $data->Mailbox->get('name') . ' <' . $data->Mailbox->get('emailAddress') . '>'; + } else { + return (string) $data; + } + } } diff --git a/modules/imap/hm-mailbox.php b/modules/imap/hm-mailbox.php index 35f6beded6..0a3880d9d6 100644 --- a/modules/imap/hm-mailbox.php +++ b/modules/imap/hm-mailbox.php @@ -218,7 +218,7 @@ public function get_message_headers($folder, $msg_id) { if ($this->is_imap()) { return $this->connection->get_message_headers($msg_id); } else { - // TODO: EWS + return $this->connection->get_message_headers($msg_id); } } @@ -233,7 +233,7 @@ public function get_message_content($folder, $msg_id, $part = 0) { if ($this->is_imap()) { return $this->connection->get_message_content($msg_id, $part); } else { - // TODO: EWS + return $this->connection->get_message_content($msg_id, $part); } } @@ -285,7 +285,7 @@ public function get_structured_message($folder, $msg_id, $part, $text_only) { } return [$msg_struct, $msg_struct_current, $msg_text, $part]; } else { - // TODO: EWS + return $this->connection->get_structured_message($msg_id, $part, $text_only); } } @@ -482,7 +482,7 @@ public function get_message_list($folder, $msg_ids) { if ($this->is_imap()) { return $this->connection->get_message_list($msg_ids); } else { - // TODO: EWS + return $this->connection->get_message_list($msg_ids); } }