Add Alternate Voicemail Location to voicemail box settings (#7932)
Adds a per-voicemail-box option that diverts the caller to an arbitrary
dialable destination instead of leaving a message. Useful for handing
voicemail off to an external service (e.g. Microsoft Teams, a paging
group, an answering service) while keeping the caller's original
Caller-ID intact for downstream logging.
The setting lives on the voicemail box (v_voicemails) — not on the
extension's follow-me/forwarding settings — so it only fires when a call
actually reaches voicemail.lua. It does not interact with the existing
"No Answer" forward on the call-forward page, nor with call-center,
ring-group, or FIFO no-answer handling: those mechanisms route the call
elsewhere before voicemail.lua ever runs, so the divert never triggers
on those paths in the first place.
Schema (v_voicemails, registered in app/voicemails/app_config.php so
upgrades pick it up automatically):
* alternate_voicemail_enabled boolean
* alternate_voicemail_destination text (any dialable string —
extension number, external
number, etc.)
UI is added to the voicemail edit screen
(app/voicemails/voicemail_edit.php) directly under "Mail To", mirroring
the toggle + destination input pattern used on the call-forward page.
Sanitisation matches the existing forward_*_destination fields
([^\*0-9] stripped).
Voicemail behaviour change is in
app/switch/resources/scripts/app/voicemail/index.lua: when a caller hits
a valid voicemail box and the box has the alternate voicemail location
enabled, the script transfers the call via session:transfer(destination,
"XML", context) BEFORE the greeting plays. session:transfer preserves
channel variables so the original Caller-ID is carried into the diverted
leg.
Override semantics — when enabled, the alternate location always wins.
The voicemail menu (voicemail_action == "check") path is unaffected, so
extension owners can still log in and review their existing messages.
Group-call safety net: the divert is intentionally skipped when the leg
is part of a call-center, ring-group, or FIFO bridge (detected via the
cc_side, cc_queue, dialed_extension, ring_group_uuid, or fifo_role
channel variables). In normal use these paths shouldn't reach the
voicemail Lua at all — the queue / group manages its own no-answer
behaviour upstream — but if one does land here for any reason, we don't
want a session:transfer to yank the leg out of the queue's control and
hijack the call to the alternate destination.
This commit is contained in:
@@ -503,6 +503,61 @@
|
||||
--valid voicemail
|
||||
if (voicemail_uuid ~= nil) then
|
||||
|
||||
--check for an alternate voicemail location on this voicemail box; when
|
||||
--enabled, divert the caller to that destination instead of leaving a
|
||||
--message. caller id is preserved (session:transfer keeps channel vars).
|
||||
--
|
||||
--this is a per-voicemail-box setting (v_voicemails table) — it only
|
||||
--fires when a call actually reaches this script with action=save, so
|
||||
--it does NOT interfere with the dialplan's forward-on-no-answer or
|
||||
--with call-center/ring-group/fifo no-answer handling. Those paths
|
||||
--never enter this script in the first place if they're configured to
|
||||
--route elsewhere before voicemail.
|
||||
--
|
||||
--the cc_side / ring_group_uuid / fifo_role / dialed_extension guard
|
||||
--is kept as a safety net: if for any reason a call-center or
|
||||
--ring-group leg lands here, we don't want a session:transfer to yank
|
||||
--it out of the queue's control.
|
||||
do
|
||||
local group_signals = {
|
||||
cc_side = session:getVariable("cc_side"),
|
||||
cc_queue = session:getVariable("cc_queue"),
|
||||
dialed_extension = session:getVariable("dialed_extension"),
|
||||
ring_group_uuid = session:getVariable("ring_group_uuid"),
|
||||
fifo_role = session:getVariable("fifo_role"),
|
||||
};
|
||||
local in_group_call = false;
|
||||
for signal_name, signal_value in pairs(group_signals) do
|
||||
if (signal_value ~= nil and signal_value ~= '') then
|
||||
in_group_call = true;
|
||||
freeswitch.consoleLog("notice", "[voicemail] alternate voicemail divert skipped: " .. signal_name .. "=" .. tostring(signal_value) .. " (call-center / ring-group / fifo leg)\n");
|
||||
break;
|
||||
end
|
||||
end
|
||||
if (not in_group_call) then
|
||||
local alt_sql = [[SELECT
|
||||
cast(alternate_voicemail_enabled as text) as alt_enabled,
|
||||
alternate_voicemail_destination
|
||||
FROM v_voicemails
|
||||
WHERE voicemail_uuid = :voicemail_uuid
|
||||
LIMIT 1]];
|
||||
local alt_params = {voicemail_uuid = voicemail_uuid};
|
||||
local alt_enabled = false;
|
||||
local alt_destination = nil;
|
||||
dbh:query(alt_sql, alt_params, function(row)
|
||||
alt_enabled = (row["alt_enabled"] == "true");
|
||||
alt_destination = row["alternate_voicemail_destination"];
|
||||
end);
|
||||
if (alt_enabled and alt_destination ~= nil and alt_destination ~= '') then
|
||||
freeswitch.consoleLog("notice", "[voicemail] alternate voicemail location enabled — diverting caller " .. (caller_id_number or '') .. " to " .. alt_destination .. " instead of leaving a message for " .. voicemail_id .. "\n");
|
||||
if (session ~= nil and session:ready()) then
|
||||
session:transfer(alt_destination, "XML", context);
|
||||
end
|
||||
return;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--play the greeting
|
||||
timeouts = 0;
|
||||
play_greeting();
|
||||
|
||||
@@ -501,6 +501,14 @@
|
||||
$apps[$x]['db'][$y]['fields'][$z]['search'] = 'true';
|
||||
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = "Enter the description.";
|
||||
$z++;
|
||||
$apps[$x]['db'][$y]['fields'][$z]['name'] = "alternate_voicemail_destination";
|
||||
$apps[$x]['db'][$y]['fields'][$z]['type'] = "text";
|
||||
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = "When set together with alternate_voicemail_enabled, callers reaching this voicemail box are transferred to this destination before the greeting plays. Caller ID is preserved.";
|
||||
$z++;
|
||||
$apps[$x]['db'][$y]['fields'][$z]['name'] = "alternate_voicemail_enabled";
|
||||
$apps[$x]['db'][$y]['fields'][$z]['type'] = "boolean";
|
||||
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = "When true and alternate_voicemail_destination is set, the call is diverted instead of leaving a message.";
|
||||
$z++;
|
||||
$apps[$x]['db'][$y]['fields'][$z]['name'] = "voicemail_name_base64";
|
||||
$apps[$x]['db'][$y]['fields'][$z]['type'] = "text";
|
||||
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = "";
|
||||
|
||||
@@ -2672,4 +2672,10 @@ $text['message-emails_resent']['zh-cn'] = "电子邮件已重新发送";
|
||||
$text['message-emails_resent']['ja-jp'] = "再送信メール";
|
||||
$text['message-emails_resent']['ko-kr'] = "이메일 재전송";
|
||||
|
||||
$text['label-alternate_voicemail']['en-us'] = "Alternate Voicemail Location";
|
||||
$text['label-alternate_voicemail']['en-gb'] = "Alternate Voicemail Location";
|
||||
|
||||
$text['description-alternate_voicemail']['en-us'] = "When enabled, callers reaching this voicemail box are routed to this destination instead of leaving a message. The original Caller ID is preserved.";
|
||||
$text['description-alternate_voicemail']['en-gb'] = "When enabled, callers reaching this voicemail box are routed to this destination instead of leaving a message. The original Caller ID is preserved.";
|
||||
|
||||
?>
|
||||
@@ -60,6 +60,8 @@
|
||||
$voicemail_option_digits = '';
|
||||
$voicemail_option_description = '';
|
||||
$voicemail_mail_to = '';
|
||||
$alternate_voicemail_enabled = false;
|
||||
$alternate_voicemail_destination = '';
|
||||
$transcribe_enabled = $settings->get('transcribe', 'enabled', false);
|
||||
|
||||
// Set variables from http GET parameters
|
||||
@@ -130,6 +132,8 @@
|
||||
$voicemail_destination = $_POST["voicemail_destination"];
|
||||
$voicemail_enabled = $_POST["voicemail_enabled"];
|
||||
$voicemail_description = $_POST["voicemail_description"];
|
||||
$alternate_voicemail_enabled = filter_var($_POST["alternate_voicemail_enabled"] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$alternate_voicemail_destination = $_POST["alternate_voicemail_destination"] ?? '';
|
||||
$voicemail_tutorial = $_POST["voicemail_tutorial"];
|
||||
$voicemail_recording_instructions = $_POST["voicemail_recording_instructions"];
|
||||
$voicemail_recording_options = $_POST["voicemail_recording_options"];
|
||||
@@ -138,6 +142,9 @@
|
||||
|
||||
//remove the space
|
||||
$voicemail_mail_to = str_replace(" ", "", $voicemail_mail_to);
|
||||
|
||||
//sanitise the alternate voicemail destination — digits and * only (matches forward_*_destination)
|
||||
$alternate_voicemail_destination = preg_replace('#[^\*0-9]#', '', $alternate_voicemail_destination);
|
||||
}
|
||||
|
||||
//process the data
|
||||
@@ -212,6 +219,8 @@
|
||||
}
|
||||
$array['voicemails'][0]['voicemail_enabled'] = $voicemail_enabled;
|
||||
$array['voicemails'][0]['voicemail_description'] = $voicemail_description;
|
||||
$array['voicemails'][0]['alternate_voicemail_enabled'] = $alternate_voicemail_enabled ? 'true' : 'false';
|
||||
$array['voicemails'][0]['alternate_voicemail_destination'] = $alternate_voicemail_destination;
|
||||
|
||||
//create permissions object
|
||||
$p = permissions::new();
|
||||
@@ -367,7 +376,9 @@
|
||||
$sql .= "voicemail_file, ";
|
||||
$sql .= "voicemail_local_after_email, ";
|
||||
$sql .= "voicemail_enabled, ";
|
||||
$sql .= "voicemail_description ";
|
||||
$sql .= "voicemail_description, ";
|
||||
$sql .= "alternate_voicemail_enabled, ";
|
||||
$sql .= "alternate_voicemail_destination ";
|
||||
$sql .= "from v_voicemails ";
|
||||
$sql .= "where domain_uuid = :domain_uuid ";
|
||||
$sql .= "and voicemail_uuid = :voicemail_uuid ";
|
||||
@@ -389,6 +400,8 @@
|
||||
$voicemail_local_after_email = $row["voicemail_local_after_email"];
|
||||
$voicemail_enabled = $row["voicemail_enabled"];
|
||||
$voicemail_description = $row["voicemail_description"];
|
||||
$alternate_voicemail_enabled = filter_var($row["alternate_voicemail_enabled"] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$alternate_voicemail_destination = $row["alternate_voicemail_destination"] ?? '';
|
||||
}
|
||||
unset($sql, $parameters, $row);
|
||||
}
|
||||
@@ -854,6 +867,28 @@
|
||||
echo "</td>\n";
|
||||
echo "</tr>\n";
|
||||
|
||||
echo "<tr>\n";
|
||||
echo "<td class='vncell' valign='top' align='left' nowrap='nowrap'>\n";
|
||||
echo " ".$text['label-alternate_voicemail']."\n";
|
||||
echo "</td>\n";
|
||||
echo "<td class='vtable' align='left'>\n";
|
||||
if ($input_toggle_style_switch) {
|
||||
echo " <span class='switch'>\n";
|
||||
}
|
||||
echo " <select class='formfld' id='alternate_voicemail_enabled' name='alternate_voicemail_enabled'>\n";
|
||||
echo " <option value='true' ".($alternate_voicemail_enabled === true ? "selected='selected'" : '').">".$text['option-true']."</option>\n";
|
||||
echo " <option value='false' ".($alternate_voicemail_enabled === false ? "selected='selected'" : '').">".$text['option-false']."</option>\n";
|
||||
echo " </select>\n";
|
||||
if ($input_toggle_style_switch) {
|
||||
echo " <span class='slider'></span>\n";
|
||||
echo " </span>\n";
|
||||
echo " ";
|
||||
}
|
||||
echo " <input class='formfld' type='text' name='alternate_voicemail_destination' id='alternate_voicemail_destination' ".($input_toggle_style_switch ? "style='margin-top: -21px;'" : null)." maxlength='255' placeholder=\"".$text['label-destination']."\" value=\"".escape($alternate_voicemail_destination ?? '')."\">\n";
|
||||
echo " <br />".$text['description-alternate_voicemail']."\n";
|
||||
echo "</td>\n";
|
||||
echo "</tr>\n";
|
||||
|
||||
if (permission_exists('voicemail_sms_edit') && file_exists(dirname(__DIR__, 2).'/app/sms/')) {
|
||||
echo "<tr>\n";
|
||||
echo "<td class='vncell' valign='top' align='left' nowrap='nowrap'>\n";
|
||||
|
||||
Reference in New Issue
Block a user