Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(backend): add scheduled send functionality #1091

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e77475c
feat(backend): add scheduled send functionality resolve conflicts
amaninyumu1 Jan 3, 2025
6e35072
feat(backend): add scheduled send functionality
amaninyumu1 Jan 3, 2025
3dcce00
feat(backend): clarify reload_and_redirect logic by removing unnecess…
amaninyumu1 Jan 7, 2025
1b3678d
feat(backend): refactor the code to fix the server error Uncaught Err…
amaninyumu1 Jan 7, 2025
ba30412
feat(backend): add scheduled send functionality resolve conflicts in …
amaninyumu1 Jan 3, 2025
edfbcc7
feat(backend): change Hm_IMAP_List to Hm_Mailbox
amaninyumu1 Jan 18, 2025
37b4dc4
Send scheduled messages via ajax
josaphatim Jan 22, 2025
115fb91
Make reschedule sending in a working state
josaphatim Jan 23, 2025
aa31793
Make background sending scheduled messages work
josaphatim Jan 23, 2025
3221593
Fix showing wait message while no draft is saving
josaphatim Jan 23, 2025
b10344e
Fix using Hm_IMAP_List instead of Hm_Mailbox
josaphatim Jan 23, 2025
34fcf4b
Fix selenium tests
josaphatim Jan 23, 2025
f62d980
feat(backend): Removing SMTP connections using the Mailbox class
amaninyumu1 Jan 29, 2025
4ad32cc
feat(backend): update hm-ews to support these headers
amaninyumu1 Jan 29, 2025
9ad1d59
feat(backend): refactor code based on review feedback
amaninyumu1 Jan 29, 2025
aed9a95
feat (backend): refactor event handling and variable declarations - E…
amaninyumu1 Feb 10, 2025
fcbc065
feat(backend): Fix php error Call to a member function authed() on null
amaninyumu1 Feb 10, 2025
cfab9e2
Use Hm_Mailbox in place of Hm_SMTP_List
josaphatim Feb 11, 2025
dfa6839
Save UTC time to fix delayed scheduled message sending
josaphatim Feb 11, 2025
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
1 change: 1 addition & 0 deletions language/az.php
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@
'You must provide a name for your script' => false,
'Empty script' => false,
'Please create a profile for saving sent messages option' => false,
'You have %d scheduled messages that won\'t be executed if you quit' => false,
'Attachment storage unavailable, please contact your site administrator' => false,
'Your subject is empty!' => false,
'Your body is empty!' => false,
Expand Down
1 change: 1 addition & 0 deletions language/de.php
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@
'You must provide a name for your script' => false,
'Empty script' => false,
'Please create a profile for saving sent messages option' => false,
'You have %d scheduled messages that won\'t be executed if you quit' => false,
'Attachment storage unavailable, please contact your site administrator' => false,
'Your subject is empty!' => false,
'Your body is empty!' => false,
Expand Down
1 change: 1 addition & 0 deletions language/en.php
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,7 @@
'You must provide a name for your script' => false,
'Empty script' => false,
'Please create a profile for saving sent messages option' => false,
'You have %d scheduled messages that won\'t be executed if you quit' => false,
'Attachment storage unavailable, please contact your site administrator' => false,
'Your subject is empty!' => false,
'Your body is empty!' => false,
Expand Down
1 change: 1 addition & 0 deletions language/es.php
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@
'You must provide a name for your script' => false,
'Empty script' => false,
'Please create a profile for saving sent messages option' => false,
'You have %d scheduled messages that won\'t be executed if you quit' => false,
'Attachment storage unavailable, please contact your site administrator' => false,
'Your subject is empty!' => false,
'Your body is empty!' => false,
Expand Down
1 change: 1 addition & 0 deletions language/et.php
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,7 @@
'You must provide a name for your script' => false,
'Empty script' => false,
'Please create a profile for saving sent messages option' => false,
'You have %d scheduled messages that won\'t be executed if you quit' => false,
'Attachment storage unavailable, please contact your site administrator' => false,
'Your subject is empty!' => false,
'Your body is empty!' => false,
Expand Down
1 change: 1 addition & 0 deletions language/fa.php
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,7 @@
'You must provide a name for your script' => false,
'Empty script' => false,
'Please create a profile for saving sent messages option' => false,
'You have %d scheduled messages that won\'t be executed if you quit' => false,
'Attachment storage unavailable, please contact your site administrator' => false,
'Your subject is empty!' => false,
'Your body is empty!' => false,
Expand Down
1 change: 1 addition & 0 deletions language/fr.php
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,7 @@
'You must provide a name for your script' => false,
'Empty script' => false,
'Please create a profile for saving sent messages option' => false,
'You have %d scheduled messages that won\'t be executed if you quit' => 'Vous avez %d messages programmés qui ne seront pas exécutés si vous quittez',
'Attachment storage unavailable, please contact your site administrator' => false,
'Your subject is empty!' => false,
'Your body is empty!' => false,
Expand Down
1 change: 1 addition & 0 deletions language/hu.php
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@
'You must provide a name for your script' => false,
'Empty script' => false,
'Please create a profile for saving sent messages option' => false,
'You have %d scheduled messages that won\'t be executed if you quit' => false,
'Attachment storage unavailable, please contact your site administrator' => false,
'Your subject is empty!' => false,
'Your body is empty!' => false,
Expand Down
1 change: 1 addition & 0 deletions language/id.php
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,7 @@
'You must provide a name for your script' => false,
'Empty script' => false,
'Please create a profile for saving sent messages option' => false,
'You have %d scheduled messages that won\'t be executed if you quit' => false,
'Attachment storage unavailable, please contact your site administrator' => false,
'Your subject is empty!' => false,
'Your body is empty!' => false,
Expand Down
1 change: 1 addition & 0 deletions language/it.php
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@
'You must provide a name for your script' => false,
'Empty script' => false,
'Please create a profile for saving sent messages option' => false,
'You have %d scheduled messages that won\'t be executed if you quit' => false,
'Attachment storage unavailable, please contact your site administrator' => false,
'Your subject is empty!' => false,
'Your body is empty!' => false,
Expand Down
1 change: 1 addition & 0 deletions language/ja.php
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@
'You must provide a name for your script' => false,
'Empty script' => false,
'Please create a profile for saving sent messages option' => false,
'You have %d scheduled messages that won\'t be executed if you quit' => false,
'Attachment storage unavailable, please contact your site administrator' => false,
'Your subject is empty!' => false,
'Your body is empty!' => false,
Expand Down
1 change: 1 addition & 0 deletions language/nl.php
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@
'You must provide a name for your script' => false,
'Empty script' => false,
'Please create a profile for saving sent messages option' => false,
'You have %d scheduled messages that won\'t be executed if you quit' => false,
'Attachment storage unavailable, please contact your site administrator' => false,
'Your subject is empty!' => false,
'Your body is empty!' => false,
Expand Down
1 change: 1 addition & 0 deletions language/pt-BR.php
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,7 @@
'You must provide a name for your script' => false,
'Empty script' => false,
'Please create a profile for saving sent messages option' => false,
'You have %d scheduled messages that won\'t be executed if you quit' => false,
'Attachment storage unavailable, please contact your site administrator' => false,
'Your subject is empty!' => false,
'Your body is empty!' => false,
Expand Down
1 change: 1 addition & 0 deletions language/ro.php
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,7 @@
'You must provide a name for your script' => false,
'Empty script' => false,
'Please create a profile for saving sent messages option' => false,
'You have %d scheduled messages that won\'t be executed if you quit' => false,
'Attachment storage unavailable, please contact your site administrator' => false,
'Your subject is empty!' => false,
'Your body is empty!' => false,
Expand Down
1 change: 1 addition & 0 deletions language/ru.php
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,7 @@
'You must provide a name for your script' => false,
'Empty script' => false,
'Please create a profile for saving sent messages option' => false,
'You have %d scheduled messages that won\'t be executed if you quit' => false,
'Attachment storage unavailable, please contact your site administrator' => false,
'Your subject is empty!' => false,
'Your body is empty!' => false,
Expand Down
1 change: 1 addition & 0 deletions language/zh-Hans.php
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,7 @@
'You must provide a name for your script' => '请提供脚本名称',
'Empty script' => '空脚本',
'Please create a profile for saving sent messages option' => '请创建用于保存已发送信息选项的配置文件',
'You have %d scheduled messages that won\'t be executed if you quit' => false,
'Attachment storage unavailable, please contact your site administrator' => '附件存储不可用,请联系您的网站管理员',
'Your subject is empty!' => '主题为空!',
'Your body is empty!' => '内容为空!',
Expand Down
26 changes: 26 additions & 0 deletions lib/servers.php
Original file line number Diff line number Diff line change
Expand Up @@ -273,4 +273,30 @@ private static function match($server, $user, $name) {
}
return false;
}

private static function appendPasswordAndUsername(array $server) {
$server['password'] = $server['pass'];
$server['username'] = $server['user'];
return $server;
}

public static function getForMailbox($id) {
$server = self::get($id, true);
if ($server) {
return self::appendPasswordAndUsername($server);
}
return false;
}

public static function dumpForMailbox($id = false) {
$list = self::dump($id, true);
if ($id !== false) {
return self::appendPasswordAndUsername($list);
}
foreach ($list as $index => $server) {
$server = self::appendPasswordAndUsername($server);
$list[$index] = $server;
}
return $list;
}
}
109 changes: 109 additions & 0 deletions modules/core/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -637,3 +637,112 @@ function privacy_setting_callback($val, $key, $mod) {
}
return $val;
}

if (!hm_exists('get_scheduled_date')) {
function get_scheduled_date($format, $only_label = false) {
switch ($format) {
case 'later_in_day':
$date_string = 'today 18:00';
$label = 'Later in the day';
break;
case 'tomorrow':
$date_string = '+1 day 08:00';
$label = 'Tomorrow';
break;
case 'next_weekend':
$date_string = 'next Saturday 08:00';
$label = 'Next weekend';
break;
case 'next_week':
$date_string = 'next week 08:00';
$label = 'Next week';
break;
case 'next_month':
$date_string = 'next month 08:00';
$label = 'Next month';
break;
default:
$date_string = $format;
$label = 'Certain date';
break;
}

$time = strtotime($date_string);

if ($only_label) {
return [$label, date('D, H:i', $time)];
}

return date('D, d M Y H:i T', $time);
}
}


/**
* @subpackage imap/functions
*/
if (!hm_exists('nexter_formats')) {
function nexter_formats() {
$values = array(
'tomorrow',
'next_weekend',
'next_week',
'next_month'
);
if (date('H') <= 16) {
array_push($values, 'later_in_day');
}
return $values;
}}

if (!hm_exists('schedule_dropdown')) {
function schedule_dropdown($output, $send_now = false) {
$values = nexter_formats();

$txt = '';
if ($send_now) {
$txt .= '<div class="dropdown d-inline-block">
<button type="button" class="btn btn-light btn-sm dropdown-toggle" id="dropdownMenuNexterDate" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="true">'.$output->trans('Reschedule').'</button>';
}
$txt .= '<ul class="dropdown-menu nexter_dropdown schedule_dropdown" aria-labelledby="dropdownMenuNexterDate">';
foreach ($values as $format) {
$labels = get_scheduled_date($format, true);
$txt .= '<li><a href="#" class="nexter_date_helper dropdown-item d-flex justify-content-between gap-5" data-value="'.$format.'"><span>'.$output->trans($labels[0]).'</span> <span class="text-end">'.$labels[1].'</span></a></li>';
}
$txt .= '<li><hr class="dropdown-divider"></li>';
$txt .= '<li><label for="nexter_input_date" class="nexter_date_picker dropdown-item cursor-pointer">'.$output->trans('Pick a date').'</label>';
$txt .= '<input id="nexter_input_date" type="datetime-local" min="'.date('Y-m-d\Th:m').'" class="nexter_input_date" style="visibility: hidden; position: absolute; height: 0;">';
$txt .= '<input class="nexter_input" style="display:none;"></li>';
if ($send_now) {
$txt .= '<li><hr class="dropdown-divider"></li>';
$txt .= '<li><a href="#" data-value="now" class="nexter_date_helper dropdown-item"">'.$output->trans('Send now').'</a></li>';
}
$txt .= '</ul>';
if ($send_now) {
$txt .= '</div>';
}

return $txt;
}}

/**
* @subpackage imap/functions
*/
if (!hm_exists('parse_delayed_header')) {
function parse_delayed_header($header, $name)
{
$header = str_replace("$name: ", '', $header);
$result = [];
foreach (explode(';', $header) as $keyValue)
{
$keyValue = trim($keyValue);
$spacePos = strpos($keyValue, ' ');
if ($spacePos > 0) {
$result[rtrim(substr($keyValue, 0, $spacePos), ':')] = trim(substr($keyValue, $spacePos+1));
} else {
$result[$keyValue] = true;
}
}
return $result;
}
}
2 changes: 1 addition & 1 deletion modules/core/handler_modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -1159,7 +1159,7 @@ public function process() {
return;
}

add_profile($profileName, $profileSignature, $profileReplyTo, $profileIsDefault, $email, $imapAddress, $email, $this->smtp_server_id, $this->imap_server_id, $this);
add_profile($profileName, $profileSignature, $profileReplyTo, $profileIsDefault, $email, $onlyJmap? $jmapAddress: $imapAddress, $email, $this->smtp_server_id, $this->imap_server_id, $this);
}

if ($this->module_is_supported('imap_folders')) {
Expand Down
7 changes: 7 additions & 0 deletions modules/core/hm-mailbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ public function server_type() {
}

public function authed() {
if (! $this->connection) {
return false;
}
if ($this->is_imap()) {
return $this->connection->get_state() == 'authenticated' || $this->connection->get_state() == 'selected';
} elseif ($this->is_smtp()) {
Expand Down Expand Up @@ -586,4 +589,8 @@ public function select_folder($folder) {
}
return true;
}

public function get_config() {
return $this->config;
}
}
6 changes: 3 additions & 3 deletions modules/core/message_list_functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -336,11 +336,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': '';
$delayed_class = isset($vals[2]) && $vals[2]? ' delayed_date': '';
if ($style == 'news') {
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('<div class="msg_date%s">%s<input type="hidden" class="msg_timestamp" value="%s" /></div>', $delayed_class, $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]));
return sprintf('<td class="msg_date%s" title="%s">%s<input type="hidden" class="msg_timestamp" value="%s" /></td>', $delayed_class, $output_mod->html_safe(date('r', $vals[1])), $output_mod->html_safe($vals[0]), $output_mod->html_safe($vals[1]));
}}

function dates_holders_callback($vals) {
Expand Down
2 changes: 1 addition & 1 deletion modules/core/site.css
Original file line number Diff line number Diff line change
Expand Up @@ -1276,7 +1276,7 @@ div.unseen,
.mobile .imap_sort {
width: 100%;
}
.snoozed_date {
.delayed_date {
color: var(--bs-primary) !important;
}

Expand Down
54 changes: 54 additions & 0 deletions modules/core/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -2405,3 +2405,57 @@ const observeMessageTextMutationAndHandleExternalResources = (inline) => {
});
}
};

function setupActionSchedule(callback) {
$(document).on('click', '.nexter_date_picker', function (e) {
document.querySelector('.nexter_input_date').showPicker();
});
$(document).on('click', '.nexter_date_helper', function (e) {
e.preventDefault();
$('.nexter_input').val($(this).attr('data-value')).trigger('change');
});
$(document).on('input', '.nexter_input_date', function (e) {
var now = new Date();
now.setMinutes(now.getMinutes() + 1);
$(this).attr('min', now.toJSON().slice(0, 16));
if (new Date($(this).val()).getTime() <= now.getTime()) {
$('.nexter_date_picker').css('border', '1px solid red');
} else {
$('.nexter_date_picker').css({ 'border': 'unset', 'border-top': '1px solid #ddd' });
}
});
$(document).on('change', '.nexter_input_date', function (e) {
const selectedDate = new Date($(this).val());
if ($(this).val() && new Date().getTime() < selectedDate.getTime()) {
$('.nexter_input').val(selectedDate.toISOString()).trigger('change');
}
});
$(document).on('change', '.nexter_input', callback);
}

function setupActionSnooze(callback) {
$(document).on('click', '.nexter_date_picker_snooze', function (e) {
document.querySelector('.nexter_input_date_snooze').showPicker();
});
$(document).on('click', '.nexter_date_helper_snooze', function (e) {
e.preventDefault();
$('.nexter_input_snooze').val($(this).attr('data-value')).trigger('change');
});
$(document).on('input', '.nexter_input_date_snooze', function (e) {
var now = new Date();
now.setMinutes(now.getMinutes() + 1);
$(this).attr('min', now.toJSON().slice(0, 16));
if (new Date($(this).val()).getTime() <= now.getTime()) {
$('.nexter_date_picker_snooze').css('border', '1px solid red');
} else {
$('.nexter_date_picker_snooze').css({ 'border': 'unset', 'border-top': '1px solid #ddd' });
}
});
$(document).on('change', '.nexter_input_date_snooze', function (e) {
const selectedDate = new Date($(this).val());
if ($(this).val() && new Date().getTime() < selectedDate.getTime()) {
$('.nexter_input_snooze').val(selectedDate.toISOString()).trigger('change');
}
});
$(document).on('change', '.nexter_input_snooze', callback);
}
Loading