Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions config/config.inc.php.sample
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,8 @@ $config['plugins'] = [

// skin name: folder from skins/
$config['skin'] = 'elastic';
// Backend to use for compose_data storage.
//Can either be 'session' (default), 'redis', 'memcache', 'apc', or 'db'
$config['compose_data_storage'] = 'session';
// Lifetime of compose data storage. Possible units: s, m, h, d, w
$config['compose_data_storage_ttl'] = '24h';
5 changes: 5 additions & 0 deletions config/defaults.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -1567,3 +1567,8 @@
// 0 - Reply-All always
// 1 - Reply-List if mailing list is detected
$config['reply_all_mode'] = 0;
// Backend to use for compose_data storage.
//Can either be 'session' (default), 'redis', 'memcache', 'apc', or 'db'
$config['compose_data_storage'] = 'session';
// Lifetime of compose data storage. Possible units: s, m, h, d, w
$config['compose_data_storage_ttl'] = '24h';
7 changes: 1 addition & 6 deletions program/actions/mail/attachment_upload.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,7 @@ public function run($args = [])
public static function init()
{
self::$COMPOSE_ID = rcube_utils::get_input_string('_id', rcube_utils::INPUT_GPC);
self::$COMPOSE = null;
self::$SESSION_KEY = 'compose_data_' . self::$COMPOSE_ID;

if (self::$COMPOSE_ID && !empty($_SESSION[self::$SESSION_KEY])) {
self::$COMPOSE = &$_SESSION[self::$SESSION_KEY];
}
self::$COMPOSE = rcmail_action_mail_compose::get_compose_data(self::$COMPOSE_ID);

if (!self::$COMPOSE) {
exit('Invalid session var!');
Expand Down
136 changes: 127 additions & 9 deletions program/actions/mail/compose.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,17 @@ public function run($args = [])
self::$COMPOSE_ID = rcube_utils::get_input_string('_id', rcube_utils::INPUT_GET);
self::$COMPOSE = null;

if (self::$COMPOSE_ID && !empty($_SESSION['compose_data_' . self::$COMPOSE_ID])) {
self::$COMPOSE = &$_SESSION['compose_data_' . self::$COMPOSE_ID];
if (self::$COMPOSE_ID && !empty(self::get_compose_data(self::$COMPOSE_ID))) {
self::$COMPOSE = self::get_compose_data(self::$COMPOSE_ID);
}

// give replicated session storage some time to synchronize
$retries = 0;
while (self::$COMPOSE_ID && !is_array(self::$COMPOSE) && $rcmail->db->is_replicated() && $retries++ < 5) {
usleep(500000);
$rcmail->session->reload();
if ($_SESSION['compose_data_' . self::$COMPOSE_ID]) {
self::$COMPOSE = &$_SESSION['compose_data_' . self::$COMPOSE_ID];
if (self::get_compose_data(self::$COMPOSE_ID)) {
self::$COMPOSE = self::get_compose_data(self::$COMPOSE_ID);
}
}

Expand All @@ -74,15 +74,14 @@ public function run($args = [])
self::$COMPOSE_ID = uniqid(mt_rand());
$params = rcube_utils::request2param(rcube_utils::INPUT_GET, 'task|action', true);

$_SESSION['compose_data_' . self::$COMPOSE_ID] = [
'id' => self::$COMPOSE_ID,
'param' => $params,
self::$COMPOSE = [
'id' => self::$COMPOSE_ID,
'param' => $params,
'mailbox' => isset($params['mbox']) && strlen($params['mbox'])
? $params['mbox'] : $rcmail->storage->get_folder(),
];

self::$COMPOSE = &$_SESSION['compose_data_' . self::$COMPOSE_ID];
self::process_compose_params(self::$COMPOSE);
self::set_compose_data(self::$COMPOSE_ID, self::$COMPOSE);

// check if folder for saving sent messages exists and is subscribed (#1486802)
if (!empty(self::$COMPOSE['param']['sent_mbox'])) {
Expand Down Expand Up @@ -336,6 +335,8 @@ public function run($args = [])

self::spellchecker_init();

self::set_compose_data(self::$COMPOSE_ID, self::$COMPOSE);

$rcmail->output->send('compose');
}

Expand Down Expand Up @@ -1730,4 +1731,121 @@ public static function quote_text($text)

return rtrim($out, "\n");
}

/**
* Handles storage actions (get, set, remove) for compose data using the configured backend.
*
* @param string $id The compose data identifier
* @param int $type The action type: 0 = get, 1 = set, 2 = remove
* @param mixed $data Optional data to store (used for set)
* @return mixed|null The compose data for get, or null for set/remove
* @throws Exception If an invalid storage type or action is provided
*/
private static function _compose_storage_action($id, $type, $data = null) {
$compose_data = null;
$rcmail = rcmail::get_instance();
$storage_type = $rcmail->config->get('compose_data_storage', 'session');

switch ($storage_type) {
case 'session':
$key = "compose_data_$id";
switch ($type) {
case 0:
$compose_data = $_SESSION[$key];
break;

case 1:
$_SESSION[$key] = $data;
break;

case 2:
$rcmail->session->remove($key);
break;

default:
throw new Exception("Invalide storage type");

}
break;

case 'apc':
case 'db':
case 'redis':
case 'memcache':
$ttl = $rcmail->config->get('compose_data_ttl', '8h');
$compose_data = call_user_func([$rcmail->get_cache('compose_data', $storage_type, $ttl), self::_get_cache_function($type)], $id, $data); //$id instead of $key for avoid redundant name in cache
break;

default:
$plugin = $rcmail->plugins->exec_hook(self::_get_cache_function($type).'_compose_data', ['id' => $id, 'storage_type' => $storage_type, 'data' => $data, 'compose_data' => null]);
if (isset($plugin['compose_data'])) $compose_data = $plugin['compose_data'];
break;
}

return $compose_data;
}

/**
* Returns the cache function name corresponding to the action type.
*
* @param int $type The action type: 0 = get, 1 = set, 2 = remove
* @return string The cache function name ('get', 'set', or 'remove')
* @throws Exception If an invalid action type is provided
*/
private static function _get_cache_function($type) : string {
$cache_function = null;
switch ($type) {
case 0:
$cache_function = 'get';
break;

case 1:
$cache_function = 'set';
break;

case 2:
$cache_function = 'remove';
break;

default:
throw new Exception("Invalide storage type");

}

return $cache_function;
}

/**
* Retrieve compose data by ID from the configured storage backend.
*
* @param string $id The compose data identifier
* @return mixed The compose data if found, or null otherwise
*/
public static function get_compose_data($id) {
return self::_compose_storage_action($id, 0);
}

/**
* Store compose data by ID in the configured storage backend.
*
* @param string $id The compose data identifier
* @param mixed $data The compose data to store
* @return mixed The stored compose data
*/
public static function set_compose_data($id, $data) {
self::_compose_storage_action($id, 1, $data);

return $data;
}

/**
* Remove compose data by ID from the configured storage backend.
*
* @param string $id The compose data identifier
* @return void
*/
public static function remove_compose_data($id) : void {
self::_compose_storage_action($id, 2);
}

}
6 changes: 4 additions & 2 deletions program/actions/mail/send.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function run($args = [])
$rcmail->output->framed = true;

$COMPOSE_ID = rcube_utils::get_input_string('_id', rcube_utils::INPUT_GPC);
$COMPOSE = &$_SESSION['compose_data_' . $COMPOSE_ID];
$COMPOSE = rcmail_action_mail_compose::get_compose_data($COMPOSE_ID);

// Sanity checks
if (!isset($COMPOSE['id'])) {
Expand Down Expand Up @@ -253,6 +253,8 @@ public function run($args = [])
}

if ($saved) {
rcmail_action_mail_compose::set_compose_data($COMPOSE_ID, $COMPOSE);

$plugin = $rcmail->plugins->exec_hook('message_draftsaved', [
'msgid' => $message_id,
'uid' => $saved,
Expand Down Expand Up @@ -296,7 +298,7 @@ public function run($args = [])
$save_error = true;
} else {
$rcmail->delete_uploaded_files($COMPOSE_ID);
$rcmail->session->remove('compose_data_' . $COMPOSE_ID);
rcmail_action_mail_compose::remove_compose_data($COMPOSE_ID);
$_SESSION['last_compose_session'] = $COMPOSE_ID;

$rcmail->output->command('remove_compose_data', $COMPOSE_ID);
Expand Down
Loading