summaryrefslogtreecommitdiff
path: root/misc/freeswitch/scripts/dialplan/functions.lua
diff options
context:
space:
mode:
Diffstat (limited to 'misc/freeswitch/scripts/dialplan/functions.lua')
-rw-r--r--misc/freeswitch/scripts/dialplan/functions.lua839
1 files changed, 839 insertions, 0 deletions
diff --git a/misc/freeswitch/scripts/dialplan/functions.lua b/misc/freeswitch/scripts/dialplan/functions.lua
new file mode 100644
index 0000000..c104f89
--- /dev/null
+++ b/misc/freeswitch/scripts/dialplan/functions.lua
@@ -0,0 +1,839 @@
+-- DialplanModule: Functions
+--
+module(...,package.seeall)
+
+Functions = {}
+
+-- Create Functions object
+function Functions.new(self, arg)
+ arg = arg or {}
+ object = arg.object or {}
+ setmetatable(object, self)
+ self.__index = self
+ self.log = arg.log
+ self.database = arg.database
+ self.domain = arg.domain
+ return object
+end
+
+function Functions.ensure_caller_sip_account(self, caller)
+ if caller.account and caller.account.class == 'sipaccount' then
+ return caller.account;
+ end
+end
+
+function Functions.dialplan_function(self, caller, dialed_number)
+ require 'common.str'
+ local parameters = common.str.to_a(dialed_number, '%-');
+ if not parameters[2] then
+ return { continue = false, code = 484, phrase = 'Malformed function parameters', no_cdr = true };
+ end
+ local fid = tostring(parameters[2]);
+ local result = { continue = false, code = 404, phrase = 'Function not found', no_cdr = true };
+
+ self.log:debug('DIALPLAN_DUNCTION - execute: ', dialed_number);
+
+ if fid == "ta" then
+ result = self:transfer_all(caller, parameters[3]);
+ elseif fid == "in" then
+ result = self:intercept_extensions(caller, parameters[3]);
+ elseif fid == "ia" then
+ result = self:intercept_any_extension(caller, parameters[3]);
+ elseif fid == "anc" then
+ result = self:account_node_change(caller);
+ elseif fid == "li" then
+ result = self:user_login(caller, parameters[3], parameters[4]);
+ elseif fid == "lo" then
+ result = self:user_logout(caller);
+ elseif fid == "lir" then
+ result = self:user_login_redirect(caller, parameters[3], parameters[4]);
+ elseif fid == "loaon" then
+ result = self:user_auto_logout(caller, true);
+ elseif fid == "loaoff" then
+ result = self:user_auto_logout(caller, false);
+ elseif fid == "dcliroff" then
+ result = self:dial_clir_off(caller, parameters[3]);
+ elseif fid == "dcliron" then
+ result = self:dial_clir_on(caller, parameters[3]);
+ elseif fid == "clipon" then
+ result = self:clip_on(caller);
+ elseif fid == "clipoff" then
+ result = self:clip_off(caller);
+ elseif fid == "cwaoff" then
+ result = self:callwaiting_off(caller);
+ elseif fid == "cwaon" then
+ result = self:callwaiting_on(caller);
+ elseif fid == "cfoff" then
+ result = self:call_forwarding_off(caller);
+ elseif fid == "cfdel" then
+ result = self:call_forwarding_off(caller, nil, true);
+ elseif fid == "cfu" then
+ result = self:call_forwarding_on(caller, 'always', parameters[3], 'PhoneNumber');
+ elseif fid == "cfuoff" then
+ result = self:call_forwarding_off(caller, 'always');
+ elseif fid == "cfudel" then
+ result = self:call_forwarding_off(caller, 'always', true);
+ elseif fid == "cfutg" then
+ result = self:call_forwarding_toggle(caller, 'always', parameters[3]);
+ elseif fid == "cfn" then
+ result = self:call_forwarding_on(caller, 'noanswer', parameters[3], 'PhoneNumber', parameters[4]);
+ elseif fid == "cfnoff" then
+ result = self:call_forwarding_off(caller, 'noanswer');
+ elseif fid == "cfndel" then
+ result = self:call_forwarding_off(caller, 'noanswer', true);
+ elseif fid == "cfo" then
+ result = self:call_forwarding_on(caller, 'offline', parameters[3], 'PhoneNumber');
+ elseif fid == "cfooff" then
+ result = self:call_forwarding_off(caller, 'offline');
+ elseif fid == "cfodel" then
+ result = self:call_forwarding_off(caller, 'offline', true);
+ elseif fid == "cfb" then
+ result = self:call_forwarding_on(caller, 'busy', parameters[3], 'PhoneNumber');
+ elseif fid == "cfboff" then
+ result = self:call_forwarding_off(caller, 'busy');
+ elseif fid == "cfbdel" then
+ result = self:call_forwarding_off(caller, 'busy', true);
+ elseif fid == "vmleave" then
+ result = self:voicemail_message_leave(caller, parameters[3]);
+ elseif fid == "vmcheck" then
+ result = self:voicemail_check(caller, parameters[3]);
+ elseif fid == "vmtg" then
+ result = self:call_forwarding_toggle(caller, nil, parameters[3]);
+ elseif fid == "acdmtg" then
+ result = self:acd_membership_toggle(caller, parameters[3], parameters[4]);
+ elseif fid == "e164" then
+ result = "+" .. tostring(parameters[3]);
+ elseif fid == "hangup" then
+ result = self:hangup(caller, parameters[3], parameters[4]);
+ end
+
+ return result;
+end
+
+-- Transfer all calls to a conference
+function Functions.transfer_all(self, caller, destination_number)
+ self.log:info('TRANSFER_ALL - caller: ', caller.account_type, '/', caller.account_uuid, ' number: ', destination_number);
+
+ local caller_sip_account = self:ensure_caller_sip_account(caller);
+ if not caller_sip_account then
+ self.log:error('TRANSFER_ALL - incompatible caller');
+ return { continue = false, code = 403, phrase = 'Incompatible caller' }
+ end
+
+ -- Query call and channel table for channel IDs
+ local sql_query = 'SELECT `b`.`name` AS `caller_chan_name`, `a`.`caller_uuid`, `a`.`callee_uuid` \
+ FROM `calls` `a` JOIN `channels` `b` ON `a`.`caller_uuid` = `b`.`uuid` JOIN `channels` `c` \
+ ON `a`.`callee_uuid` = `c`.`uuid` WHERE `b`.`name` LIKE ("%' .. caller_sip_account.record.auth_name .. '@%") \
+ OR `c`.`name` LIKE ("%' .. caller_sip_account.record.auth_name .. '@%") LIMIT 100';
+
+ self.database:query(sql_query, function(call_entry)
+ local uid = nil
+ if call_entry.caller_chan_name:find(caller_sip_account.record.auth_name .. "@") then
+ uid = call_entry.callee_uuid;
+ self.log:debug("Transfering callee channel with uid: " .. uid);
+ else
+ uid = call_entry.caller_uuid;
+ self.log:debug("Transfering caller channel with uid: " .. uid);
+ end
+ freeswitch.API():execute("uuid_transfer", uid .. " " .. destination_number);
+ end)
+
+ return destination_number;
+end
+
+-- Intercept Extensions
+function Functions.intercept_extensions(self, caller, destination_numbers)
+ if type(destination_numbers) == "string" then
+ destination_numbers = "\"" .. destination_numbers .. "\"";
+ else
+ destination_numbers = "\"" .. table.concat(destination_numbers, "\",\"") .. "\"";
+ end
+
+ self.log:debug("Intercept call to number(s): " .. destination_numbers);
+
+ if caller.account_type ~= "SipAccount" then
+ self.log:error("caller is not a SipAccount");
+ return { continue = false, code = 403, phrase = 'Incompatible caller' }
+ end
+
+ local sql_query = 'SELECT * FROM `channels` WHERE `callstate` IN ("EARLY", "ACTIVE") AND `dest` IN (' .. destination_numbers .. ') LIMIT 1';
+
+ self.database:query(sql_query, function(call_entry)
+ self.log:debug("intercepting call with uid: " .. call_entry.uuid);
+ caller:intercept(call_entry.uuid);
+ end)
+
+ return nil;
+end
+
+-- intercept call to destination (e.g. sip_account)
+function Functions.intercept_destination(self, caller, destination)
+ self.log:debug("Intercept call to destination " .. destination);
+ local result = false;
+ local sql_query = 'SELECT `call_uuid`, `uuid` FROM `channels` WHERE `callstate` = "RINGING" AND `dest` = "' .. destination .. '" LIMIT 1';
+
+ caller:set_caller_id(caller.caller_phone_numbers[1] ,caller.caller_id_name);
+ self.database:query(sql_query, function(call_entry)
+ if call_entry.call_uuid and tostring(call_entry.call_uuid) then
+ self.log:debug("intercepting call - uuid: " .. call_entry.call_uuid);
+ caller:intercept(call_entry.call_uuid);
+ result = { continue = false, code = 200, call_service = 'pickup' }
+ require 'common.str'
+ require 'common.fapi'
+ local fapi = common.fapi.FApi:new{ log = self.log, uuid = call_entry.call_uuid }
+ if fapi:channel_exists() then
+ caller:set_caller_id(
+ common.str.to_s(fapi:get_variable('effective_caller_id_number')),
+ common.str.to_s(fapi:get_variable('effective_caller_id_name'))
+ );
+ caller:set_callee_id(
+ common.str.to_s(fapi:get_variable('effective_callee_id_number')),
+ common.str.to_s(fapi:get_variable('effective_callee_id_name'))
+ );
+
+ caller:set_variable('gs_destination_type', fapi:get_variable('gs_destination_type'));
+ caller:set_variable('gs_destination_id', fapi:get_variable('gs_destination_id'));
+ caller:set_variable('gs_destination_uuid', fapi:get_variable('gs_destination_uuid'));
+
+ caller:set_variable('gs_caller_account_type', fapi:get_variable('gs_account_type'));
+ caller:set_variable('gs_caller_account_id', fapi:get_variable('gs_account_id'));
+ caller:set_variable('gs_caller_account_uuid', fapi:get_variable('gs_account_uuid'));
+
+ caller:set_variable('gs_auth_account_type', fapi:get_variable('gs_auth_account_type'));
+ caller:set_variable('gs_auth_account_id', fapi:get_variable('gs_auth_account_id'));
+ caller:set_variable('gs_auth_account_uuid', fapi:get_variable('gs_auth_account_uuid'));
+ end
+ else
+ self.log:error('FUNCTION - failed to intercept call - no caller uuid for callee uuid: ', call_entry.uuid);
+ end
+ end)
+
+ return result;
+end
+
+-- intercept call to owner of destination_number
+function Functions.intercept_any_extension(self, caller, destination_number)
+ require 'common.phone_number'
+ local phone_number_object = common.phone_number.PhoneNumber:new{ log = self.log, database = self.database }:find_by_number(destination_number);
+
+ if not phone_number_object or not phone_number_object.record then
+ self.log:notice("unallocated number: " .. tostring(destination_number));
+ return false;
+ end
+
+ if phone_number_object.record.phone_numberable_type == 'SipAccount' then
+ require "common.sip_account"
+ local sip_account_class = common.sip_account.SipAccount:new{ log = self.log, database = self.database }
+ local sip_account = sip_account_class:find_by_id(phone_number_object.record.phone_numberable_id)
+ if sip_account then
+ return self:intercept_destination(caller, sip_account.record.auth_name);
+ end
+ end
+end
+
+
+function Functions.account_node_change(self, caller)
+ self.log:info('NODE_CHANGE - caller: ', caller.account_type, '/', caller.account_uuid, ', caller_id: ', caller.caller_id_number);
+
+ -- find caller's sip account
+ local caller_sip_account = caller.account;
+ if not caller_sip_account or not caller_sip_account.class == 'sipaccount' then
+ self.log:notice('LOGIN - caller sip_account not found');
+ return { continue = false, code = 404, phrase = 'Account not found', no_cdr = true }
+ end
+
+ require 'phones.phone'
+ local phone_class = phones.phone.Phone:new{log = self.log, database = self.database}
+
+ -- logout caller phones if caller account is hot-deskable
+ local caller_phones = phone_class:find_all_hot_deskable_by_account(caller_sip_account.record.id);
+ for index, phone_caller in ipairs(caller_phones) do
+ phone_caller:logout(caller_sip_account.record.id);
+ end
+
+ self:update_node_change(caller_sip_account, caller.local_node_id);
+ caller:answer();
+ caller:send_display('Change successful');
+ caller.session:sayPhrase('logged_in');
+
+ -- resync caller phones
+ for index, phone_caller in ipairs(caller_phones) do
+ local result = phone_caller:resync{ auth_name = caller_sip_account.auth_name, domain = caller.domain };
+ self.log:info('NODE_CHANGE - resync phone - mac: ', phone_caller.record.mac_address, ', ip_address: ', phone_caller.record.ip_address, ', result: ', result);
+ end
+
+ return { continue = false, code = 200, phrase = 'OK', no_cdr = true }
+end
+
+
+function Functions.user_login(self, caller, number, pin)
+ require 'common.str'
+
+ local PHONE_NUMBER_LEN_MIN = 4;
+ local PHONE_NUMBER_LEN_MAX = 12;
+ local PIN_LEN_MIN = 4;
+ local PIN_LEN_MAX = 12;
+
+ caller:set_variable('destination_number', 'f-li-' .. common.str.to_s(number) .. '-PIN');
+ self.log:info('LOGIN - caller: ', caller.account_type, '/', caller.account_uuid, ', caller_id: ', caller.caller_id_number, ', number: ', number);
+
+ if common.str.blank(number) then
+ number = caller.session:read(PHONE_NUMBER_LEN_MIN, PHONE_NUMBER_LEN_MAX, 'ivr/ivr-please_enter_extension_followed_by_pound.wav', 3000, '#');
+ end
+
+ -- find caller's sip account
+ local caller_sip_account = caller.account;
+ if not caller_sip_account or not caller_sip_account.class == 'sipaccount' then
+ self.log:notice('LOGIN - caller sip_account not found');
+ return { continue = false, code = 404, phrase = 'Caller not found', no_cdr = true }
+ end
+
+ require 'phones.phone'
+ local phone_class = phones.phone.Phone:new{log = self.log, database = self.database}
+
+ local caller_phones = phone_class:find_all_hot_deskable_by_account(caller_sip_account.id);
+ local caller_phone = caller_phones[1];
+
+ if not caller_phone then
+ self.log:notice('LOGIN - caller phone not found or not hot-deskable');
+ return { continue = false, code = 403, phrase = 'Phone not hot-deskable', no_cdr = true }
+ end
+
+ require 'common.phone_number'
+ local phone_number = common.phone_number.PhoneNumber:new{log = self.log, database = self.database}:find_by_number(number, {"SipAccount"});
+
+ if not phone_number then
+ self.log:notice('LOGIN - number not found or not linked to a sip account - number: ', number);
+ return { continue = false, code = 404, phrase = 'Account not found', no_cdr = true }
+ end
+
+ require 'common.sip_account'
+ local destination_sip_account = common.sip_account.SipAccount:new{ log = self.log, database = self.database }:find_by_id(phone_number.record.phone_numberable_id);
+
+ if not destination_sip_account then
+ self.log:notice('LOGIN - account not found - ', phone_number.record.phone_numberable_type, '=', phone_number.record.phone_numberable_id, ', number: ', number);
+ return { continue = false, code = 404, phrase = 'Account not found', no_cdr = true }
+ end
+
+ self.log:info('LOGIN - destination: ', phone_number.record.phone_numberable_type, '=', destination_sip_account.record.id,
+ ', caller_name: ', destination_sip_account.record.caller_name, ', hotdeskable: ', destination_sip_account.record.hotdeskable);
+
+ if not common.str.to_b(destination_sip_account.record.hotdeskable) then
+ self.log:notice('LOGIN - destination sip_account not hot-deskable');
+ return { continue = false, code = 404, phrase = 'Destination not hot-deskable', no_cdr = true }
+ end
+
+ require 'dialplan.user'
+ local user = dialplan.user.User:new{ log = self.log, database = self.database }:find_by_id(destination_sip_account.record.sip_accountable_id);
+
+ if common.str.blank(pin) then
+ pin = caller.session:read(PIN_LEN_MIN, PIN_LEN_MAX, 'ivr/ivr-please_enter_pin_followed_by_pound.wav', 3000, '#');
+ end
+
+ if not user then
+ self.log:notice('LOGIN - user not found - ', destination_sip_account.record.sip_accountable_type, '=',destination_sip_account.record.sip_accountable_id);
+ return { continue = false, code = 403, phrase = 'Authentication failed', no_cdr = true }
+ end
+
+ if not user:check_pin(pin) then
+ self.log:notice('LOGIN - authentication failed');
+ return { continue = false, code = 403, phrase = 'Authentication failed', no_cdr = true }
+ end
+
+ -- logout caller phones if caller account is hot-deskable
+ if common.str.to_b(caller_sip_account.record.hotdeskable) then
+ for index, phone_caller in ipairs(caller_phones) do
+ phone_caller:logout(caller_sip_account.record.id);
+ end
+ end
+
+ local destination_phones = phone_class:find_all_hot_deskable_by_account(destination_sip_account.record.id);
+ -- logout destination phones
+ for index, phone_destination in ipairs(destination_phones) do
+ phone_destination:logout(destination_sip_account.record.id);
+ end
+
+ local result = caller_phone:login(destination_sip_account.record.id, destination_sip_account.record.sip_accountable_id, destination_sip_account.record.sip_accountable_type);
+ self.log:info('LOGIN - account login - mac: ', caller_phone.record.mac_address, ', ip_address: ', caller_phone.record.ip_address, ', result: ', result);
+
+ if not result then
+ return { continue = false, code = 403, phrase = 'Login failed', no_cdr = true }
+ end
+
+ caller:answer();
+ caller:send_display('Login successful');
+
+ self:update_node_change(destination_sip_account, caller.local_node_id);
+ caller:sleep(1000);
+
+ -- resync destination phones
+ for index, phone_destination in ipairs(destination_phones) do
+ local result = phone_destination:resync{ auth_name = destination_sip_account.auth_name, domain = caller.domain_local };
+ self.log:info('LOGIN - resync destination phone - mac: ', phone_destination.record.mac_address, ', ip_address: ', phone_destination.record.ip_address, ', result: ', result);
+ end
+
+ -- resync caller phones
+ for index, phone_caller in ipairs(caller_phones) do
+ local result = phone_caller:resync{ auth_name = caller_sip_account.auth_name, domain = caller.domain };
+ self.log:info('LOGIN - resync caller phone - mac: ', phone_caller.record.mac_address, ', ip_address: ', phone_caller.record.ip_address, ', result: ', result);
+ end
+
+ return { continue = false, code = 200, phrase = 'OK', no_cdr = true }
+end
+
+
+function Functions.user_logout(self, caller)
+ require 'common.str'
+ self.log:info('LOGOUT - caller: ', caller.account_type, '/', caller.account_uuid, ', caller_id: ', caller.caller_id_number);
+
+ -- find caller's sip account
+ local caller_sip_account = caller.account;
+ if not caller_sip_account or not caller_sip_account.class == 'sipaccount' then
+ self.log:notice('LOGOUT - caller sip_account not found');
+ return { continue = false, code = 404, phrase = 'Caller not found', no_cdr = true }
+ end
+
+ if not common.str.to_b(caller_sip_account.record.hotdeskable) then
+ self.log:notice('LOGOUT - caller sip_account not hot-deskable');
+ return { continue = false, code = 404, phrase = 'Caller not hot-deskable', no_cdr = true }
+ end
+
+ require 'phones.phone'
+ local phone_class = phones.phone.Phone:new{log = self.log, database = self.database}
+
+ local caller_phones = phone_class:find_all_hot_deskable_by_account(caller_sip_account.id);
+
+ if caller_phones == 0 then
+ self.log:notice('LOGOUT - caller phones not found or not hot-deskable');
+ return { continue = false, code = 403, phrase = 'Phone not hot-deskable', no_cdr = true }
+ end
+
+ local result = false;
+ for index, phone_caller in ipairs(caller_phones) do
+ result = phone_caller:logout(caller_sip_account.record.id);
+ self.log:info('LOGOUT - account logout - mac: ', phone_caller.record.mac_address, ', ip_address: ', phone_caller.record.ip_address, ', result: ', result);
+ end
+
+ caller:answer();
+ caller:send_display('Logout successful');
+ caller:sleep(1000);
+
+ -- resync caller phones
+ for index, phone_caller in ipairs(caller_phones) do
+ local result = phone_caller:resync{ auth_name = caller_sip_account.auth_name, domain = caller.domain };
+ self.log:info('LOGIN - resync caller phone - mac: ', phone_caller.record.mac_address, ', ip_address: ', phone_caller.record.ip_address, ', result: ', result);
+ end
+
+ return { continue = false, code = 200, phrase = 'OK', no_cdr = true }
+end
+
+
+function Functions.update_node_change(self, sip_account, node_id)
+ require 'common.sync_log'
+ local sync_log_class = common.sync_log.SyncLog:new{ log = self.log, database = self.database, homebase_ip_address = sip_account.record.host }
+
+ if tostring(sip_account.record.gs_node_id) ~= tostring(node_id) then
+ self.log:info('UPDATE_NODE - from: ', sip_account.record.gs_node_id, ', to: ', node_id, ', sipaccount=', sip_account.record.id, '/', sip_account.record.uuid, '@', node_id, ', caller_name: ', sip_account.record.caller_name);
+ sql_query = 'UPDATE `sip_accounts` SET `updated_at` = NOW(), `gs_node_id` = ' .. tonumber(node_id) .. ' WHERE id = ' .. tonumber(sip_account.record.id);
+ if self.database:query(sql_query) then
+ sync_log_class:insert('SipAccount', { uuid = sip_account.record.uuid, gs_node_id = tonumber(node_id), updated_at = os.date('!%Y-%m-%d %H:%M:%S %Z') }, 'update', { 'gs_node_id', 'updated_at' });
+ end
+ end
+
+ require 'common.phone_number'
+ local phone_numbers = common.phone_number.PhoneNumber:new{log = self.log, database = self.database}:find_all_by_owner(sip_account.record.id, 'SipAccount');
+ for number_id, phone_number in pairs(phone_numbers) do
+ if tostring(phone_number.record.gs_node_id) ~= tostring(node_id) then
+ self.log:info('UPDATE_NODE - from: ', phone_number.record.gs_node_id, ', to: ', node_id, ', phonenumber=', phone_number.record.id, '/', phone_number.record.uuid, '@', node_id, ', number: ', phone_number.record.number);
+ sql_query = 'UPDATE `phone_numbers` SET `updated_at` = NOW(), `gs_node_id` = ' .. tonumber(node_id) .. ' WHERE id = ' .. tonumber(number_id);
+
+ if self.database:query(sql_query) then
+ sync_log_class:insert('PhoneNumber', { uuid = phone_number.record.uuid, gs_node_id = tonumber(node_id), updated_at = os.date('!%Y-%m-%d %H:%M:%S %Z') }, 'update', { 'gs_node_id', 'updated_at' });
+ end
+ end
+ end
+end
+
+
+function Functions.user_login_redirect(self, caller, phone_number, pin)
+ -- Remove PIN from destination_number
+ caller.session:setVariable("destination_number", "f-li-" .. tostring(phone_number) .. "-PIN");
+
+ -- Redirect to f-li function
+ caller.session:execute("redirect", "sip:f-li-" .. tostring(phone_number) .. "-" .. tostring(pin) .. "@" .. caller.domain);
+end
+
+-- Set nightly_reboot flag
+function Functions.user_auto_logout(self, caller, auto_logout)
+ local nightly_reboot = 'FALSE';
+ if auto_logout then
+ nightly_reboot = 'TRUE';
+ end
+
+ -- Ensure a valid sip account
+ local caller_sip_account = caller.account;
+ if not caller_sip_account or not caller_sip_account.class == 'sipaccount' then
+ return { continue = false, code = 403, phrase = 'Incompatible caller', no_cdr = true }
+ end
+
+ require "phones.phone"
+ local phone_class = phones.phone.Phone:new{log = self.log, database = self.database}
+
+ -- Get caller phone
+ local caller_phone = phone_class:find_hot_deskable_by_account(caller_sip_account.id);
+ if not caller_phone then
+ self.log:notice("Caller phone not found or not hot-deskable");
+ return { continue = false, code = 401, phrase = 'Phone not hot-deskable', no_cdr = true }
+ end
+
+ log:debug("Hot-desking auto log off - caller phone: " .. caller_phone.record.id .. ", mac: " .. caller_phone.record.mac_address);
+
+ sql_query = 'UPDATE `phones` SET `nightly_reboot` = ' .. nightly_reboot .. ' WHERE `id` = ' .. tonumber(caller_phone.record.id);
+
+ if not self.database:query(sql_query) then
+ self.log:error('Hot-desking auto log off status could not be changed from ' .. tostring(caller_phone.record.nightly_reboot) .. ' to ' .. nightly_reboot);
+ return { continue = false, code = 401, phrase = 'Value could not be changed', no_cdr = true }
+
+ end
+
+ self.log:debug('Hot-desking auto log off changed from ' .. tostring(caller_phone.record.nightly_reboot) .. ' to ' .. nightly_reboot);
+
+ caller:answer();
+ caller:send_display('Logout successful');
+ caller:sleep(1000);
+end
+
+function Functions.dial_clir_off(self, caller, phone_number)
+ -- Ensure a valid sip account
+ local caller_sip_account = self:ensure_caller_sip_account(caller);
+ if not caller_sip_account then
+ return { continue = false, code = 403, phrase = 'Incompatible caller', no_cdr = true }
+ end
+
+ caller.clir = false;
+ return { continue = true, number = phone_number }
+end
+
+function Functions.dial_clir_on(self, caller, phone_number)
+ -- Ensure a valid sip account
+ local caller_sip_account = self:ensure_caller_sip_account(caller);
+ if not caller_sip_account then
+ return { continue = false, code = 403, phrase = 'Incompatible caller', no_cdr = true }
+ end
+
+ caller.clir = true;
+ return { continue = true, number = phone_number }
+end
+
+function Functions.callwaiting_on(self, caller)
+ -- Find caller's SipAccount
+ local caller_sip_account = self:ensure_caller_sip_account(caller);
+ if not caller_sip_account then
+ return { continue = false, code = 403, phrase = 'Incompatible caller', no_cdr = true }
+ end
+
+ local sql_query = 'UPDATE `sip_accounts` SET `call_waiting` = TRUE WHERE `id` = ' .. caller_sip_account.record.id;
+
+ if not self.database:query(sql_query) then
+ self.log:notice("Call Waiting could not be set");
+ return { continue = false, code = 500, phrase = 'Call Waiting could not be set', no_cdr = true }
+ end
+
+ caller:answer();
+ caller:send_display('Call waiting on');
+ caller:sleep(1000);
+ return { continue = false, code = 200, phrase = 'OK', no_cdr = true }
+end
+
+function Functions.callwaiting_off(self, caller)
+ -- Find caller's SipAccount
+ local caller_sip_account = self:ensure_caller_sip_account(caller);
+ if not caller_sip_account then
+ return { continue = false, code = 403, phrase = 'Incompatible caller', no_cdr = true }
+ end
+
+ local sql_query = 'UPDATE `sip_accounts` SET `call_waiting` = FALSE WHERE `id` = ' .. caller_sip_account.record.id;
+
+ if not self.database:query(sql_query) then
+ self.log:notice("Call Waiting could not be set");
+ return { continue = false, code = 500, phrase = 'Call Waiting could not be set', no_cdr = true }
+ end
+
+ caller:answer();
+ caller:send_display('Call waiting off');
+ caller:sleep(1000);
+ return { continue = false, code = 200, phrase = 'OK', no_cdr = true }
+end
+
+function Functions.clip_on(self, caller)
+ -- Find caller's SipAccount
+ local caller_sip_account = self:ensure_caller_sip_account(caller);
+ if not caller_sip_account then
+ return { continue = false, code = 403, phrase = 'Incompatible caller', no_cdr = true }
+ end
+
+ local sql_query = 'UPDATE `sip_accounts` SET `clip` = TRUE WHERE `id` = ' .. caller_sip_account.record.id;
+
+ if not self.database:query(sql_query) then
+ self.log:notice("CLIP could not be set");
+ return { continue = false, code = 500, phrase = 'CLIP could not be set', no_cdr = true }
+
+ end
+
+ caller:answer();
+ caller:send_display('CLIP on');
+ caller:sleep(1000);
+ return { continue = false, code = 200, phrase = 'OK', no_cdr = true }
+end
+
+function Functions.clip_off(self, caller)
+ -- Find caller's SipAccount
+ local caller_sip_account = self:ensure_caller_sip_account(caller);
+ if not caller_sip_account then
+ return { continue = false, code = 403, phrase = 'Incompatible caller', no_cdr = true }
+ end
+
+ local sql_query = 'UPDATE `sip_accounts` SET `clip` = FALSE WHERE `id` = ' .. caller_sip_account.record.id;
+
+ if not self.database:query(sql_query) then
+ self.log:notice("CLIP could not be set");
+ return { continue = false, code = 500, phrase = 'CLIP could not be set', no_cdr = true }
+
+ end
+
+ caller:answer();
+ caller:send_display('CLIP off');
+ caller:sleep(1000);
+ return { continue = false, code = 200, phrase = 'OK', no_cdr = true }
+end
+
+
+function Functions.call_forwarding_off(self, caller, call_forwarding_service, delete)
+ local defaults = {log = self.log, database = self.database, domain = caller.domain}
+
+ -- Find caller's SipAccount
+ local caller_sip_account = self:ensure_caller_sip_account(caller);
+ if not caller_sip_account then
+ return { continue = false, code = 403, phrase = 'Incompatible caller', no_cdr = true }
+ end
+
+ require 'common.phone_number'
+ local phone_number_class = common.phone_number.PhoneNumber:new{ log = self.log, database = self.database, domain = caller.domain };
+ local phone_numbers = phone_number_class:list_by_owner(caller_sip_account.record.id, 'SipAccount');
+
+ local success = false;
+ for index, phone_number in pairs(phone_numbers) do
+ phone_number_object = phone_number_class:find_by_number(phone_number);
+ if phone_number_object then
+ if phone_number_object:call_forwarding_off(call_forwarding_service, nil, delete) then
+ success = true;
+ end
+ end
+ end
+
+ if not success then
+ self.log:notice("call forwarding could not be deactivated");
+ return { continue = false, code = 500, phrase = 'Call Forwarding could not be deactivated', no_cdr = true }
+
+ end
+
+ caller:answer();
+ caller:send_display('Call forwarding off');
+ caller:sleep(1000);
+ return { continue = false, code = 200, phrase = 'OK', no_cdr = true }
+end
+
+
+function Functions.call_forwarding_on(self, caller, call_forwarding_service, destination, destination_type, timeout)
+ local defaults = {log = self.log, database = self.database, domain = caller.domain}
+
+ if not call_forwarding_service then
+ self.log:notice('no call forwarding service specified');
+ end
+
+ -- Find caller's SipAccount
+ local caller_sip_account = self:ensure_caller_sip_account(caller);
+ if not caller_sip_account then
+ return { continue = false, code = 403, phrase = 'Incompatible caller', no_cdr = true }
+ end
+
+ require "common.phone_number"
+ local phone_number_class = common.phone_number.PhoneNumber:new{ log = self.log, database = self.database, domain = caller.domain };
+ local phone_numbers = phone_number_class:list_by_owner(caller_sip_account.record.id, 'SipAccount');
+
+ local success = false;
+ for index, phone_number in pairs(phone_numbers) do
+ phone_number_object = phone_number_class:find_by_number(phone_number);
+ if phone_number_object then
+ if phone_number_object:call_forwarding_on(call_forwarding_service, destination, timeout) then
+ success = true;
+ end
+ end
+ end
+
+ if not success then
+ self.log:notice("call forwarding could not be activated");
+ return { continue = false, code = 500, phrase = 'Call Forwarding could not be activated', no_cdr = true }
+
+ end
+
+ caller:answer();
+ caller:send_display('Call forwarding on');
+ caller:sleep(1000);
+ return { continue = false, code = 200, phrase = 'OK', no_cdr = true }
+end
+
+
+function Functions.call_forwarding_toggle(self, caller, call_forwarding_service, phone_number_id)
+ local defaults = {log = self.log, database = self.database, domain = caller.domain}
+
+ -- Find caller's SipAccount
+ local caller_sip_account = self:ensure_caller_sip_account(caller);
+ if not caller_sip_account then
+ return { continue = false, code = 403, phrase = 'Incompatible caller', no_cdr = true }
+ end
+
+ require "common.phone_number"
+ local phone_number_class = common.phone_number.PhoneNumber:new{ log = self.log, database = self.database, domain = caller.domain };
+ local phone_numbers = phone_number_class:list_by_owner(caller_sip_account.record.id, 'SipAccount');
+
+ local result = nil;
+ for index, phone_number in pairs(phone_numbers) do
+ phone_number_object = phone_number_class:find_by_number(phone_number);
+ if phone_number_object then
+ if not result then
+ result = phone_number_object:call_forwarding_toggle(call_forwarding_service);
+ elseif result.active then
+ phone_number_object:call_forwarding_on(call_forwarding_service, result.destination, result.destination_type, result.timeout);
+ else
+ phone_number_object:call_forwarding_off(call_forwarding_service);
+ end
+ end
+ end
+
+ if not result then
+ self.log:notice("call forwarding could not be toggled");
+ return { continue = false, code = 500, phrase = 'Call Forwarding could not be toggled', no_cdr = true }
+
+ end
+
+ caller:answer();
+ caller:send_display('Call forwarding toggled');
+ caller:sleep(1000);
+ return { continue = false, code = 200, phrase = 'OK', no_cdr = true }
+end
+
+
+function Functions.voicemail_message_leave(self, caller, phone_number)
+ require 'dialplan.voicemail'
+ local voicemail_account = dialplan.voicemail.Voicemail:new{ log = self.log, database = self.database }:find_by_number(phone_number);
+
+ if not voicemail_account then
+ return { continue = false, code = 404, phrase = 'Mailbox not found', no_cdr = true }
+ end
+
+ voicemail_account:leave(caller, phone_number);
+
+ if caller:to_s("voicemail_message_len") ~= '' then
+ voicemail_account:send_notify(caller);
+ else
+ self.log:debug("voicemail - no message saved");
+ end
+
+ return { continue = false, code = 200, phrase = 'OK' }
+end
+
+
+function Functions.voicemail_check(self, caller, phone_number)
+ local voicemail_account = nil;
+ local voicemail_authorized = false;
+
+ require 'dialplan.voicemail'
+
+ if phone_number then
+ voicemail_account = dialplan.voicemail.Voicemail:new{ log = self.log, database = self.database }:find_by_number(phone_number);
+ else
+ if caller.auth_account_type == 'SipAccount' then
+ voicemail_account = dialplan.voicemail.Voicemail:new{ log = self.log, database = self.database }:find_by_sip_account_id(caller.auth_account.id);
+ voicemail_authorized = true;
+ end
+ end
+
+ if not voicemail_account then
+ return { continue = false, code = 404, phrase = 'Mailbox not found', no_cdr = true }
+ end
+
+ voicemail_account:menu(caller, voicemail_authorized);
+
+ return { continue = false, code = 200, phrase = 'OK', no_cdr = true }
+end
+
+
+function Functions.acd_membership_toggle(self, caller, agent_id, phone_number)
+ -- Find caller's SipAccount
+ local caller_sip_account = self:ensure_caller_sip_account(caller);
+ if not caller_sip_account then
+ return { continue = false, code = 403, phrase = 'Incompatible caller', no_cdr = true }
+ end
+
+ require 'dialplan.acd'
+ local acd_class = dialplan.acd.AutomaticCallDistributor:new{ log = self.log, database = self.database, domain = self.domain };
+
+ self.log:info('ACD_MEMBERSHIP_TOGGLE - sipaccount=', caller_sip_account.id, '/', caller_sip_account.uuid, ', agent=', agent_id, ', ACD phone number: ', phone_number);
+
+ if not tonumber(agent_id) or tonumber(agent_id) == 0 then
+
+ if not phone_number then
+ self.log:notice('ACD_MEMBERSHIP_TOGGLE - neither agent_id nor phone_number specified');
+ return { continue = false, code = 404, phrase = 'Agent not found', no_cdr = true }
+ end
+
+ require "common.phone_number"
+ local phone_number_object = common.phone_number.PhoneNumber:new{ log = self.log, database = self.database, domain = caller.domain }:find_by_number(phone_number, {'AutomaticCallDistributor'});
+
+ if not phone_number_object or not tonumber(phone_number_object.record.phone_numberable_id) then
+ self.log:notice('ACD_MEMBERSHIP_TOGGLE - ACD not found');
+ return { continue = false, code = 404, phrase = 'ACD not found', no_cdr = true }
+ end
+
+ local agent = acd_class:agent_find_by_acd_and_destination(phone_number_object.record.phone_numberable_id, caller_sip_account.class, caller_sip_account.id);
+
+ if not agent or not tonumber(agent.id) then
+ self.log:notice('ACD_MEMBERSHIP_TOGGLE - agent not found');
+ return { continue = false, code = 404, phrase = 'Agent not found', no_cdr = true }
+ end
+
+ agent_id = agent.id;
+ end
+
+ local status = acd_class:agent_status_toggle(agent_id, 'sipaccount', caller_sip_account.id);
+
+ if not status then
+ self.log:error('ACD_MEMBERSHIP_TOGGLE - error toggling ACD membership');
+ return { continue = false, code = 500, phrase = 'Error toggling ACD membership', no_cdr = true }
+ end
+
+ self.log:info('ACD_MEMBERSHIP_TOGGLE - sipaccount=', caller_sip_account.id, '/', caller_sip_account.uuid, ', agent=', agent_id, ', status: ', status);
+
+ caller:answer();
+ caller:send_display('ACD membership toggled: ' .. status);
+ caller:sleep(500);
+ caller.session:sayPhrase('acd_agent_status', tostring(status));
+ return { continue = false, code = 200, phrase = 'OK', no_cdr = true }
+end
+
+function Functions.hangup(self, caller, code, phrase)
+ require 'common.str'
+
+ if not tonumber(code) then
+ code = 403;
+ phrase = 'Forbidden';
+ end
+
+ if common.str.blank(phrase) then
+ phrase = 'Hangup here';
+ end
+
+ self.log:info("FUNCTION_HANGUP code: ", code, ', phrase: ', phrase);
+ return { continue = false, code = code, phrase = phrase:gsub('_', ' '), no_cdr = true }
+end