Skip to content
Open
5 changes: 3 additions & 2 deletions config/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -430,10 +430,11 @@
| only one email source, this option is less likely to be a problem.
|
|
| 'allow_session_cache' => env('ALLOW_SESSION_CACHE', false),
| 'cache_class' => env('CACHE_CLASS')
*/

'allow_session_cache' => env('ALLOW_SESSION_CACHE', false),

'cache_class' => env('CACHE_CLASS'),

/*
| -------------
Expand Down
71 changes: 49 additions & 22 deletions modules/sievefilters/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -449,21 +449,17 @@ function block_filter_dropdown ($mod, $mailbox_id = null, $with_scope = true, $s
}

if (!hm_exists('get_blocked_senders_array')) {
function get_blocked_senders_array($mailbox, $site_config, $user_config)
function get_blocked_senders_array($current_script, $scripts)
{
$factory = get_sieve_client_factory($site_config);
try {
$client = $factory->init($user_config, $mailbox, in_array(mb_strtolower('nux'), $site_config->get_modules(true), true));
$scripts = $client->listScripts();

if (array_search('blocked_senders', $scripts, true) === false) {
if (!is_array($scripts) || array_search('blocked_senders', $scripts, true) === false) {
return [];
}

$blocked_senders = [];
$current_script = $client->getScript('blocked_senders');
if ($current_script != '') {
$blocked_list = prepare_sieve_script ($current_script);
// $blocked_list = prepare_sieve_script ($current_script);
$base64_obj = str_replace("# ", "", preg_split('#\r?\n#', $current_script, 0)[1]);
$blocked_list = json_decode(base64_decode($base64_obj));
if (!$blocked_list) {
return [];
}
Expand All @@ -485,15 +481,11 @@ function get_blocked_senders_array($mailbox, $site_config, $user_config)
}

if (!hm_exists('get_blocked_senders')){
function get_blocked_senders($mailbox, $mailbox_id, $icon_svg, $icon_block_domain_svg, $site_config, $user_config, $module) {
$factory = get_sieve_client_factory($site_config);
function get_blocked_senders($mailbox_id, $icon_svg, $icon_block_domain_svg, $module, $scripts, $current_script) {
try {
$client = $factory->init($user_config, $mailbox, in_array(mb_strtolower('nux'), $site_config->get_modules(true), true));
$scripts = $client->listScripts();
if (array_search('blocked_senders', $scripts, true) === false) {
if (!is_array($scripts) || array_search('blocked_senders', $scripts, true) === false) {
return '';
}
$current_script = $client->getScript('blocked_senders');
$blocked_list_actions = [];
$blocked_senders = [];
if ($current_script != '') {
Expand Down Expand Up @@ -565,7 +557,45 @@ function get_blocked_senders($mailbox, $mailbox_id, $icon_svg, $icon_block_domai
function initialize_sieve_client_factory($site_config, $user_config, $imapServer) {
try {
$factory = get_sieve_client_factory($site_config);
return $factory->init($user_config, $imapServer, in_array(mb_strtolower('nux'), $site_config->get_modules(true), true));
return $factory->init(
$user_config,
$imapServer,
in_array(
mb_strtolower('nux'),
$site_config->get_modules(true),
true
)
);
} catch (Exception $e) {
Hm_Msgs::add("Sieve: {$e->getMessage()}", "danger");
return null;
}
}
}

/* *
* This function is used to get the blocked senders script and its content.
* It returns an array with the list of scripts and the current script content.
*/
if (!hm_exists('get_all_scripts')) {
function get_all_scripts($imapServer, $load_current = true, $return_only = null) {
try {
$client = SieveConnectionPool::get($imapServer);
if(!is_null($client)){
$scripts = $client->listScripts();
if (!is_array($scripts) || array_search('blocked_senders', $scripts, true) === false) {
return '';
}
$current_script = '';
if($load_current) {
$current_script = SieveConnectionPool::getScript($imapServer, 'blocked_senders');
}
if ($return_only === 'scripts') return $scripts;
if ($return_only === 'current_script') return $current_script;

return [$scripts, $current_script];
}
return null;
} catch (Exception $e) {
Hm_Msgs::add("Sieve: {$e->getMessage()}", "danger");
return null;
Expand All @@ -591,11 +621,8 @@ function get_sieve_host_from_services($imap_host) {
}

if (!hm_exists('get_sieve_linked_mailbox')) {
function get_sieve_linked_mailbox ($imap_account, $module) {
$factory = get_sieve_client_factory($site_config);
function get_sieve_linked_mailbox ($scripts, $client) {
try {
$client = $factory->init($module->user_config, $imap_account, $module->module_is_supported('nux'));
$scripts = $client->listScripts();
$folders = [];
foreach ($scripts as $s) {
$script = $client->getScript($s);
Expand All @@ -615,11 +642,11 @@ function get_sieve_linked_mailbox ($imap_account, $module) {
}

if (!hm_exists('is_mailbox_linked_with_filters')) {
function is_mailbox_linked_with_filters ($mailbox, $imap_server_id, $module) {
function is_mailbox_linked_with_filters ($mailbox, $imap_server_id, $module, $scripts, $client) {
$imap_servers = $module->user_config->get('imap_servers');
$imap_account = $imap_servers[$imap_server_id];
if (isset($imap_account['sieve_config_host'])) {
$linked_mailboxes = get_sieve_linked_mailbox($imap_account, $module);
$linked_mailboxes = get_sieve_linked_mailbox($scripts, $client);
return in_array($mailbox, $linked_mailboxes);
}
return false;
Expand Down
144 changes: 144 additions & 0 deletions modules/sievefilters/hm-sieve-pool.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php
ini_set('memory_limit', '2048M');

use PhpSieveManager\ManageSieve\Client;
class SieveConnectionPool
{
private static $config = [];
private static $connections = [];
private static $lastConnectionTimes = [];
/**
* Default timeout for connections in seconds.
*/
private static $timeout = 600;

/**
* Cache TTL in seconds
*/
private static $scriptCacheTTL = 600;

/**
* Cache instance (injected or global)
*/
private static $cache = null;

private function __construct() {}

/**
* Optionally inject a global cache instance (Hm_Cache)
*/
public static function setCache($cacheInstance)
{
if(!self::$cache) {
self::$cache = $cacheInstance;
}
}

/**
* Set the configuration for all servers
*/
public static function setConfig(array $servers)
{
if (empty($servers)) {
throw new Exception("Configuration cannot be empty.");
}
foreach ($servers as $key => $server) {
if (!isset($server['host'], $server['port'], $server['username'], $server['password'])) {
throw new Exception("Each server configuration must include 'host', 'port', 'username', and 'password'.");
}
if (!is_string($server['host']) || !is_int($server['port']) || !is_string($server['username']) || !is_string($server['password'])) {
throw new Exception("Invalid types in server configuration for '$key'.");
}
if (isset(self::$config[$key])) {
self::$config[$key] = array_merge(self::$config[$key], $server);
} else {
self::$config[$key] = $server;
}
}
}

/**
* Get a Sieve connection by its key (server identifier)
*/
public static function get($key)
{
if (!isset(self::$config[$key])) {
throw new Exception("No configuration found for '$key'");
}

if (!isset(self::$connections[$key]) || !self::isAlive($key)) {
self::$connections[$key] = self::connectServer(self::$config[$key]);
self::$lastConnectionTimes[$key] = time();
}

return self::$connections[$key];
}

/**
* Get a script by name for a given connection key with persistent caching
*/
public static function getScript($key, string $scriptName)
{
if (!self::$cache) {
throw new Exception("Cache instance not set. Call SieveConnectionPool::setCache() first.");
}

$cacheKey = "sieve_script_{$key}_{$scriptName}";

// Try to fetch from persistent cache
$cached = self::$cache->get($cacheKey, false, true);

if ($cached && isset($cached['time']) && (time() - $cached['time']) < self::$scriptCacheTTL) {
return $cached['data'];
}

// Cache miss — fetch from server
$client = self::get($key);
$script = $client->getScript($scriptName);

// Save into persistent cache
self::$cache->set($cacheKey, [
'data' => $script,
'time' => time()
], self::$scriptCacheTTL, true);

return $script;
}

/**
* Optional: clear cached script
*/
public static function clearScriptCache($key, string $scriptName)
{
if (!self::$cache) {
return false;
}
$cacheKey = "sieve_script_{$key}_{$scriptName}";
return self::$cache->del($cacheKey);
}

/**
* Check if the connection is still alive based on timeout
*/
private static function isAlive($key)
{
return isset(self::$lastConnectionTimes[$key]) &&
(time() - self::$lastConnectionTimes[$key]) < self::$timeout;
}

/**
* Establish the Sieve connection
*/
private static function connectServer(array $serverConfig)
{
$client = new Client($serverConfig['host'], $serverConfig['port']);
$client->connect(
$serverConfig['username'],
$serverConfig['password'],
$serverConfig['secure'] ?? true,
"",
$serverConfig['authType'] ?? "PLAIN"
);
return $client;
}
}
53 changes: 39 additions & 14 deletions modules/sievefilters/modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@

if (!defined('DEBUG_MODE')) { die(); }

use PhpSieveManager\ManageSieve\Client;
use PhpSieveManager\Exceptions\SocketException;

require_once APP_PATH.'modules/imap/functions.php';
require_once APP_PATH.'modules/imap/hm-imap.php';
require_once APP_PATH.'modules/sievefilters/hm-sieve.php';
require_once APP_PATH.'modules/sievefilters/hm-sieve-pool.php';
require_once APP_PATH.'modules/sievefilters/functions.php';

/**
Expand Down Expand Up @@ -1237,7 +1235,11 @@ protected function output() {
$default_behaviour_html .= '<input type="text" class="select_default_reject_message form-control" value="' . $default_reject_message . '" placeholder="' . $this->trans('Reject message') . '" />';
}
$default_behaviour_html .= '<button class="submit_default_behavior btn btn-primary">' . $this->trans('Submit') . '</button></div></div>';
$blocked_senders = get_blocked_senders_array($mailbox, $this->get('site_config'), $this->get('user_config'));
$client = SieveConnectionPool::get($mailbox['id']);
$current_script = SieveConnectionPool::getScript($mailbox['id'], 'blocked_senders');
$scripts = $client->listScripts();
list($scripts, $current_script) = get_all_scripts($mailbox['id'], true);
$blocked_senders = get_blocked_senders_array($current_script, $scripts);
$num_blocked = $blocked_senders ? sizeof($blocked_senders) : 0;
$res = '<div class="sievefilters_accounts_item">';
$res .= '<div class="sievefilters_accounts_title settings_subtitle py-2 border-bottom cursor-pointer d-flex justify-content-between" data-num-blocked="' . $num_blocked . '">' . $mailbox['name'];
Expand All @@ -1246,7 +1248,7 @@ protected function output() {
$res .= $default_behaviour_html;
$res .= '<table class="filter_details table"><tbody>';
$res .= '<tr><th class="col-sm-6">Sender</th><th class="col-sm-3">Behavior</th><th class="col-sm-3">Actions</th></tr>';
$res .= get_blocked_senders($mailbox, $mailbox['id'], 'x-circle-fill', 'globe-europe-africa', $this->get('site_config'), $this->get('user_config'), $this);
$res .= get_blocked_senders($mailbox['id'], 'x-circle-fill', 'globe-europe-africa', $this, $scripts, $current_script);
$res .= '</tbody></table>';
$res .= '</div></div></div>';
$this->out('sieve_detail_display', $res);
Expand Down Expand Up @@ -1459,12 +1461,10 @@ public function process() {
return;
}

$factory = get_sieve_client_factory($this->config);
try {
$client = $factory->init($this->user_config, $imap_account);
list($scripts, $current_script) = get_all_scripts($form['imap_server_id'], true);

$blocked_senders = [];
$current_script = $client->getScript('blocked_senders');
if ($current_script != '') {
$blocked_list = prepare_sieve_script ($current_script);
foreach ($blocked_list as $blocked_sender) {
Expand Down Expand Up @@ -1526,6 +1526,30 @@ public function process()
return;
}
$accounts = $this->get('imap_accounts');
//filter out the accounts that are not sieve enabled by sieve_config_host
$sieve_accounts = array_filter($accounts, function ($account) {
return !empty($account['sieve_config_host']);
});
$sieve_accounts_configs = [];
foreach ($sieve_accounts as $key => $item) {
$parts = explode(':', $item['sieve_config_host']);
$sieveHost = isset($parts[0]) ? $parts[0] : '';
$sievePort = isset($parts[1]) ? (int) $parts[1] : 4190;
$sieve_accounts_configs[$key] = [
'host' => $sieveHost,
'port' => $sievePort,
'username' => $item['user'],
'password' => $item['pass'],
'secure' => $item['sieve_tls'],
'authType' => 'PLAIN',
'id' => $key
];
}
// set the sieve connection pool with the sieve accounts configs
if (!empty($sieve_accounts_configs)) {
SieveConnectionPool::setCache($this->cache);
SieveConnectionPool::setConfig($sieve_accounts_configs);
}
if (isset($accounts[$form['imap_server_id']])) {
$this->out('mailbox', $accounts[$form['imap_server_id']]);
$this->session->close_early();
Expand All @@ -1547,13 +1571,11 @@ public function process()

$mailbox = Hm_IMAP_List::get_connected_mailbox($form['imap_server_id'], $this->cache);
if ($mailbox && $mailbox->authed() && $mailbox->is_imap()) {
$imap_servers = $this->user_config->get('imap_servers');
$imap_account = $imap_servers[$form['imap_server_id']];
$linked_mailboxes = get_sieve_linked_mailbox($imap_account, $this);
// list($scripts, $current_script, $client) = get_all_scripts($this->config, $this->user_config, $mailbox, true);
list($scripts, $current_script) = get_all_scripts($form['imap_server_id'], true);
$linked_mailboxes = get_sieve_linked_mailbox($scripts, $current_script);
if ($linked_mailboxes && in_array($form['folder'], $linked_mailboxes)) {
$factory = get_sieve_client_factory($this->site_config);
try {
$client = $factory->init($this->user_config, $imap_account, $this->module_is_supported('nux'));
$script_names = array_filter(
$linked_mailboxes,
function ($value) use ($form) {
Expand Down Expand Up @@ -1599,7 +1621,10 @@ public function process()
$mailbox = Hm_IMAP_List::get_connected_mailbox($form['imap_server_id'], $this->cache);
if ($mailbox && $mailbox->authed() && $mailbox->is_imap()) {
$del_folder = prep_folder_name($mailbox->get_connection(), $form['folder'], true);
if (is_mailbox_linked_with_filters($del_folder, $form['imap_server_id'], $this)) {
$client = SieveConnectionPool::get($mailbox['id']);
// list($scripts, $current_script, $client) = get_all_scripts($this->config, $this->user_config, $mailbox);
list($scripts) = get_all_scripts($form['imap_server_id'], false);
if (is_mailbox_linked_with_filters($del_folder, $form['imap_server_id'], $this, $scripts, $client)) {
$this->out('sieve_can_delete_folder', false);
Hm_Msgs::add('This folder can\'t be deleted because it is used in a Sieve filter.', 'warning');
}
Expand Down