Skip to content

Commit

Permalink
Added snooze functionnality to messages
Browse files Browse the repository at this point in the history
  • Loading branch information
josaphatim committed Jul 3, 2023
1 parent 0212c2f commit 27494ff
Show file tree
Hide file tree
Showing 9 changed files with 381 additions and 17 deletions.
5 changes: 3 additions & 2 deletions modules/core/message_list_functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,10 +281,11 @@ function subject_callback($vals, $style, $output_mod) {
*/
if (!hm_exists('date_callback')) {
function date_callback($vals, $style, $output_mod) {
$snooze_class = isset($vals[2]) && $vals[2]? ' snoozed_date': '';
if ($style == 'news') {
return sprintf('<div class="msg_date">%s<input type="hidden" class="msg_timestamp" value="%s" /></div>', $output_mod->html_safe($vals[0]), $output_mod->html_safe($vals[1]));
return sprintf('<div class="msg_date%s">%s<input type="hidden" class="msg_timestamp" value="%s" /></div>', $snooze_class, $output_mod->html_safe($vals[0]), $output_mod->html_safe($vals[1]));
}
return sprintf('<td class="msg_date" title="%s">%s<input type="hidden" class="msg_timestamp" value="%s" /></td>', $output_mod->html_safe(date('r', $vals[1])), $output_mod->html_safe($vals[0]), $output_mod->html_safe($vals[1]));
return sprintf('<td class="msg_date%s" title="%s">%s<input type="hidden" class="msg_timestamp" value="%s" /></td>', $snooze_class, $output_mod->html_safe(date('r', $vals[1])), $output_mod->html_safe($vals[0]), $output_mod->html_safe($vals[1]));
}}

/**
Expand Down
1 change: 1 addition & 0 deletions modules/core/site.css
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,4 @@ div.unseen, .unseen .subject { font-weight: 700; }
padding-bottom: 5px;
background-color: #fff;
}
.snoozed_date { color: teal !important; }
169 changes: 162 additions & 7 deletions modules/imap/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,21 @@ function format_imap_message_list($msg_list, $output_module, $parent_list=false,
$from = '[No From]';
$nofrom = ' nofrom';
}
if ($list_sort == 'date') {
$date_field = 'date';
$is_snoozed = !empty($msg['x_snoozed']) && hex2bin($msg['folder']) == 'Snoozed';
if ($is_snoozed) {
$snooze_header = parse_snooze_header('X-Snoozed: '.$msg['x_snoozed']);
$date = $snooze_header['until'];
$timestamp = strtotime($date);
} else {
$date_field = 'internal_date';
}
$timestamp = strtotime($msg[$date_field]);
$date = translate_time_str(human_readable_interval($msg[$date_field]), $output_module);
if ($list_sort == 'date') {
$date_field = 'date';
} else {
$date_field = 'internal_date';
}
$date = translate_time_str(human_readable_interval($msg[$date_field]), $output_module);
$timestamp = strtotime($msg[$date_field]);
}

$flags = array();
if (!stristr($msg['flags'], 'seen')) {
$flags[] = 'unseen';
Expand Down Expand Up @@ -284,7 +292,7 @@ function format_imap_message_list($msg_list, $output_module, $parent_list=false,
array('safe_output_callback', 'source', $source, $icon),
array('safe_output_callback', 'from'.$nofrom, $from, null, str_replace(array($from, '<', '>'), '', $msg['from'])),
array('subject_callback', $subject, $url, $flags),
array('date_callback', $date, $timestamp),
array('date_callback', $date, $timestamp, $is_snoozed),
array('icon_callback', $flags)
),
$id,
Expand Down Expand Up @@ -1144,3 +1152,150 @@ function get_personal_ns($imap) {
);
}}

/**
* @subpackage imap/functions
*/
if (!hm_exists('snooze_message')) {
function snooze_message($imap, $msg_id, $folder, $snooze_tag) {
if (!$imap->select_mailbox($folder)) {
return false;
}
if (!$snooze_tag) {
$imap->message_action('UNREAD', array($msg_id));
}
$msg = $imap->get_message_content($msg_id, 0);
preg_match("/^X-Snoozed:.*(\r?\n[ \t]+.*)*\r?\n?/im", $msg, $matches);
if (count($matches)) {
$msg = str_replace($matches[0], '', $msg);
$old_folder = parse_snooze_header($matches[0])['from'];
}
if ($snooze_tag) {
$from = $old_folder ?? $folder;
$msg = "$snooze_tag;\n \tfrom $from\n".$msg;
}
$msg = str_replace("\r\n", "\n", $msg);
$msg = str_replace("\n", "\r\n", $msg);
$msg = rtrim($msg)."\r\n";

$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, 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;
}
}
}
} else {
$snooze_headers = parse_snooze_header($matches[0]);
$original_folder = $snooze_headers['from'];
if ($imap->select_mailbox($original_folder) && $imap->append_start($original_folder, 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;
}
}
}
}
return $res;
}}

/**
* @subpackage imap/functions
*/
if (!hm_exists('parse_snooze_header')) {
function parse_snooze_header($snooze_header)
{
$snooze_header = str_replace('X-Snoozed: ', '', $snooze_header);
$result = [];
foreach (explode(';', str_replace("\r\n", " ", $snooze_header)) as $kv)
{
$kv = trim($kv);
$spacePos = strpos($kv, ' ');
if ($spacePos > 0) {
$result[rtrim(substr($kv, 0, $spacePos), ':')] = trim(substr($kv, $spacePos+1));
} else {
$result[$kv] = true;
}
}
return $result;
}}

/**
* @subpackage imap/functions
*/
if (!hm_exists('get_snooze_date')) {
function get_snooze_date($format, $only_label = false) {
if ($format == 'later_in_day') {
$date_string = 'today 18:00';
$label = 'Later in the day';
} elseif ($format == 'tomorrow') {
$date_string = '+1 day 08:00';
$label = 'Tomorrow';
} elseif ($format == 'next_weekend') {
$date_string = 'next Saturday 08:00';
$label = 'Next weekend';
} elseif ($format == 'next_week') {
$date_string = 'next week 08:00';
$label = 'Next week';
} elseif ($format == 'next_month') {
$date_string = 'next month 08:00';
$label = 'Next month';
} else {
$date_string = $format;
$label = 'Certain date';
}
$time = strtotime($date_string);
if ($only_label) {
return [$label, date('D, H:i', $time)];
}
return date('D, d M Y H:i', $time);
}}

/**
* @subpackage imap/functions
*/
if (!hm_exists('snooze_formats')) {
function snooze_formats() {
return array(
'tomorrow',
'next_weekend',
'next_week',
'next_month'
);
}}

/**
* @subpackage imap/functions
*/
if (!hm_exists('snooze_dropdown')) {
function snooze_dropdown($output, $unsnooze = false) {
if (date('H') <= 16) {
$values = array_merge(['later_in_day'], snooze_formats());
}
$txt = '<div style="display: inline-block;">';
$txt .= '<a class="snooze_link hlink" id="snooze_message" href="#">'.$output->trans('Snooze').'</a>';
$txt .= '<div class="snooze_dropdown" style="display:none;">';
foreach ($values as $format) {
$labels = get_snooze_date($format, true);
$txt .= '<a href="#" class="snooze_helper" data-value="'.$format.'">'.$output->trans($labels[0]).' <span>'.$labels[1].'</span></a>';
}
$txt .= '<label for="snooze_input_date" class="snooze_date_picker">'.$output->trans('Pick a date').'</label>';
$txt .= '<input id="snooze_input_date" type="datetime-local" min="'.date('Y-m-d\Th:m').'" class="snooze_input_date" style="visibility: hidden; position: absolute; height: 0;">';
$txt .= '<input class="snooze_input" style="display:none;">';
if ($unsnooze) {
$txt .= '<a href="#" data-value="unsnooze" class="unsnooze snooze_helper">'.$output->trans('Unsnooze').'</a>';
}
$txt .= '</div></div>';

return $txt;
}}

82 changes: 81 additions & 1 deletion modules/imap/handler_modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ public function process() {
}
}
elseif (count($moved) == 0) {
Hm_Msgs::add('ERRUnable to move/copy selected messages');
Hm_Msgs::add('ERR Unable to move/copy selected messages');
}
if ($form['imap_move_action'] == 'move' && $form['imap_move_page'] == 'message') {
$msgs = Hm_Msgs::get();
Expand Down Expand Up @@ -936,6 +936,86 @@ public function process() {
}
}

/**
* Snooze message
* @subpackage imap/handler
*/
class Hm_Handler_imap_snooze_message extends Hm_Handler_Module {
/**
* Use IMAP to snooze the selected message uid
*/
public function process() {
list($success, $form) = $this->process_form(array('imap_snooze_ids', 'imap_snooze_until'));
if (!$success) {
return;
}
$snoozed_messages = 0;
$snooze_tag = null;
if ($form['imap_snooze_until'] != 'unsnooze') {
$at = date('D, d M Y H:i:s O');
$until = get_snooze_date($form['imap_snooze_until']);
$snooze_tag = "X-Snoozed: at $at;\n \tuntil $until";
}
$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)) {
$folder = hex2bin($folder);
if (snooze_message($imap, $msg_id, $folder, $snooze_tag)) {
$snoozed_messages++;
}
}
}
$this->out('snoozed_messages', $snoozed_messages);
if ($snoozed_messages == count($ids)) {
$msg = 'Messages snoozed';
} elseif ($snoozed_messages > 0) {
$msg = 'Some messages have been snoozed';
} else {
$msg = 'ERRFailed to snooze selected messages';
}
Hm_Msgs::add($msg);
$msgs = Hm_Msgs::get();
Hm_Msgs::flush();
$this->session->secure_cookie($this->request, 'hm_msgs', base64_encode(json_encode($msgs)));
}
}

/**
* Unsnooze messages
* @subpackage imap/handler
*/
class Hm_Handler_imap_unsnooze_message extends Hm_Handler_Module {
/**
* Use IMAP unsnooze messages in snoozed directory
* This should use cron
*/
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)) {
$folder = 'Snoozed';
$ret = $imap->get_mailbox_page($folder, 'DATE', false, 'ALL');
foreach ($ret[1] as $msg) {
$msg_headers = $imap->get_message_headers($msg['uid']);
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);
}
} catch (Exception $e) {
Hm_Debug::add(sprintf('ERR Cannot unsnooze message: %s', $msg_headers['subject']));
}
}
}
}
}
}

/**
* Perform an IMAP message action
* @subpackage imap/handler
Expand Down
10 changes: 6 additions & 4 deletions modules/imap/hm-imap.php
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,7 @@ public function get_message_list($uids, $raw=false) {
if ($this->is_supported( 'X-GM-EXT-1' )) {
$command .= 'X-GM-MSGID X-GM-THRID X-GM-LABELS ';
}
$command .= "BODY.PEEK[HEADER.FIELDS (SUBJECT X-AUTO-BCC FROM DATE CONTENT-TYPE X-PRIORITY TO LIST-ARCHIVE REFERENCES MESSAGE-ID)])\r\n";
$command .= "BODY.PEEK[HEADER.FIELDS (SUBJECT X-AUTO-BCC FROM DATE CONTENT-TYPE X-PRIORITY TO LIST-ARCHIVE REFERENCES MESSAGE-ID X-SNOOZED)])\r\n";
$cache_command = $command.(string)$raw;
$cache = $this->check_cache($cache_command);
if ($cache !== false) {
Expand All @@ -736,8 +736,8 @@ public function get_message_list($uids, $raw=false) {
$res = $this->get_response(false, true);
$status = $this->check_response($res, true);
$tags = array('X-GM-MSGID' => 'google_msg_id', 'X-GM-THRID' => 'google_thread_id', 'X-GM-LABELS' => 'google_labels', 'UID' => 'uid', 'FLAGS' => 'flags', 'RFC822.SIZE' => 'size', 'INTERNALDATE' => 'internal_date');
$junk = array('X-AUTO-BCC', 'MESSAGE-ID', 'REFERENCES', 'LIST-ARCHIVE', 'SUBJECT', 'FROM', 'CONTENT-TYPE', 'TO', '(', ')', ']', 'X-PRIORITY', 'DATE');
$flds = array('x-auto-bcc' => 'x_auto_bcc', 'message-id' => 'message_id', 'references' => 'references', 'list-archive' => 'list_archive', 'date' => 'date', 'from' => 'from', 'to' => 'to', 'subject' => 'subject', 'content-type' => 'content_type', 'x-priority' => 'x_priority');
$junk = array('X-AUTO-BCC', 'MESSAGE-ID', 'REFERENCES', 'X-SNOOZED', 'LIST-ARCHIVE', 'SUBJECT', 'FROM', 'CONTENT-TYPE', 'TO', '(', ')', ']', 'X-PRIORITY', 'DATE');
$flds = array('x-auto-bcc' => 'x_auto_bcc', 'message-id' => 'message_id', 'references' => 'references', 'x-snoozed' => 'x_snoozed', 'list-archive' => 'list_archive', 'date' => 'date', 'from' => 'from', 'to' => 'to', 'subject' => 'subject', 'content-type' => 'content_type', 'x-priority' => 'x_priority');
$headers = array();
foreach ($res as $n => $vals) {
if (isset($vals[0]) && $vals[0] == '*') {
Expand All @@ -758,6 +758,7 @@ public function get_message_list($uids, $raw=false) {
$google_thread_id = '';
$google_labels = '';
$x_auto_bcc = '';
$x_snoozed = '';
$count = count($vals);
for ($i=0;$i<$count;$i++) {
if ($vals[$i] == 'BODY[HEADER.FIELDS') {
Expand Down Expand Up @@ -806,7 +807,8 @@ public function get_message_list($uids, $raw=false) {
'date' => $date, 'from' => $from, 'to' => $to, 'subject' => $subject, 'content-type' => $content_type,
'timestamp' => time(), 'charset' => $cset, 'x-priority' => $x_priority, 'google_msg_id' => $google_msg_id,
'google_thread_id' => $google_thread_id, 'google_labels' => $google_labels, 'list_archive' => $list_archive,
'references' => $references, 'message_id' => $message_id, 'x_auto_bcc' => $x_auto_bcc);
'references' => $references, 'message_id' => $message_id, 'x_auto_bcc' => $x_auto_bcc,
'x_snoozed' => $x_snoozed);

if ($raw) {
$headers[$uid] = array_map('trim', $headers[$uid]);
Expand Down
23 changes: 21 additions & 2 deletions modules/imap/output_modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ class Hm_Output_filter_message_headers extends Hm_Output_Module {
protected function output() {
if ($this->get('msg_headers')) {
$txt = '';
$small_headers = array('subject', 'date', 'from', 'to', 'cc', 'flags');
$small_headers = array('subject', 'x-snoozed', 'date', 'from', 'to', 'cc', 'flags');
$reply_args = sprintf('&amp;list_path=%s&amp;uid=%d',
$this->html_safe($this->get('msg_list_path')),
$this->html_safe($this->get('msg_text_uid'))
Expand All @@ -189,6 +189,11 @@ protected function output() {
}
$txt .= $this->html_safe($value).'</th></tr>';
}
elseif ($fld == 'x-snoozed') {
$snooze_header = parse_snooze_header($value);
$txt .= '<tr class="header_'.$fld.'"><th>';
$txt .= $this->trans('Snoozed').'</th><td>'.$this->trans('Until').' '.$this->html_safe($snooze_header['until']).' <a href="#" data-value="unsnooze" class="unsnooze snooze_helper">Unsnooze</a></td></tr>';
}
elseif ($fld == 'date') {
try {
$dt = new DateTime($value);
Expand Down Expand Up @@ -281,7 +286,8 @@ protected function output() {
$txt .= ' | <a class="delete_link hlink" id="delete_message" href="#">'.$this->trans('Delete').'</a>';
$txt .= ' | <a class="hlink" id="copy_message" href="#">'.$this->trans('Copy').'</a>';
$txt .= ' | <a class="hlink" id="move_message" href="#">'.$this->trans('Move').'</a>';
$txt .= ' | <a class="archive_link hlink" id="archive_message" href="#">'.$this->trans('Archive').'</a>';
$txt .= ' | <a class="archive_link hlink" id="archive_message" href="#">'.$this->trans('Archive').'</a>';
$txt .= ' | ' . snooze_dropdown($this, isset($headers['X-Snoozed']));

if ($this->get('sieve_filters_enabled')) {
$server_id = $this->get('msg_server_id');
Expand Down Expand Up @@ -1106,3 +1112,16 @@ protected function output() {
'<td><input type="checkbox" '.$checked.' id="review_sent_email" name="review_sent_email" value="1" />'.$reset.'</td></tr>';
}
}

/**
* Add snooze dialog to the message list controls
* @subpackage imap/output
*/
class Hm_Output_snooze_msg_control extends Hm_Output_Module {
protected function output() {
$parts = explode('_', $this->get('list_path'));
$unsnooze = $parts[0] == 'imap' && hex2bin($parts[2]) == 'Snoozed';
$res = snooze_dropdown($this, $unsnooze);
$this->concat('msg_controls_extra', $res);
}
}
Loading

0 comments on commit 27494ff

Please sign in to comment.