Add. Support `bridge` mode to call forward from Lua. (#1631)

This commit is contained in:
Alexey Melnichuk 2016-06-08 16:33:55 +03:00 committed by FusionPBX
parent 96d576fc7a
commit add8222cff
2 changed files with 297 additions and 1 deletions

View File

@ -45,6 +45,7 @@
local log = require "resources.functions.log".call_forward
local cache = require "resources.functions.cache"
local Database = require "resources.functions.database"
local route_to_bridge = require "resources.functions.route_to_bridge"
local function opt(t, ...)
if select('#', ...) == 0 then
@ -265,7 +266,20 @@
dial_string = dial_string .. "user/"..forward_all_destination.."@"..domain_name;
end
else
dial_string = dial_string .. "loopback/"..forward_all_destination;
local mode = opt(settings(domain_uuid), 'domain', 'bridge', 'text')
if mode == "outbound" or mode == "bridge" then
local bridge = route_to_bridge(dbh, domain_uuid, {
destination_number = forward_all_destination;
['${toll_allow}'] = toll_allow;
})
if bridge and bridge.bridge then
dial_string = dial_string .. bridge.bridge
else
log.warning('Can not build dialstring for call forward number.')
end
else
dial_string = dial_string .. "loopback/"..forward_all_destination;
end
end
end

View File

@ -0,0 +1,282 @@
local log = require "resources.functions.log".route_to_bridge
local pcre_match
if freeswitch then
--! @todo find better way to extract captures
api = api or freeswitch.API()
local unpack = unpack or table.unpack
function pcre_match(str, pat)
local a = str .. "|/" .. pat .."/"
if api:execute("regex", a) == 'false' then return end
local t = {}
for i = 1, 5 do
t[i] = api:execute("regex", a .. '|$' .. i)
end
return unpack(t)
end
else
local pcre = require "rex_pcre"
function pcre_match(str, pat)
return pcre.match(str, pat)
end
end
local function pcre_self_test()
local a, b, c
a,b,c = pcre_match('abcd', '(\\d{3})(\\d{3})')
assert(a == nil)
a,b,c = pcre_match('123456', '(\\d{3})(\\d{3})')
assert(a == '123', a)
assert(b == '456', b)
end
local select_routes_sql = [[
select *
from v_dialplans
where (domain_uuid = '%s' or domain_uuid is null)
and app_uuid = '8c914ec3-9fc0-8ab5-4cda-6c9288bdc9a3'
and dialplan_enabled = 'true'
order by dialplan_order asc
]]
local select_extensions_sql = [[
select * from v_dialplan_details
where dialplan_uuid = '%s'
order by dialplan_detail_group asc, dialplan_detail_order asc
]]
local function append(t, v)
t[#t + 1] = v
return t
end
local function order_keys(t)
local o = {}
for k in pairs(t) do append(o, k) end
table.sort(o)
local i = 0
return function(o)
i = i + 1
return o[i], t[o[i]]
end, o
end
local function check_conditions(group, fields)
local matches, pass, last, break_on
for n, condition in ipairs(group.conditions) do
last = (n == #group.conditions)
local value = fields[condition.type]
if (not value) and (condition.type ~= '') then -- skip unkonw fields
log.errf('Unsupportded condition: %s', condition.type)
matches, pass = {}, false
else
if condition.type == '' then
matches, pass = {}, true
else
matches = {pcre_match(value, condition.data)}
pass = #matches > 0
end
log.debugf('%s condition %s(%s) to `%s`', pass and 'PASS' or 'FAIL', condition.type, condition.data, value or '<NONE>')
end
break_on = condition.break_on
if break_on == 'always' then break end
if break_on ~= 'never' then
if pass and break_on == 'on-true' then break end
if not pass and (break_on == 'on-false' or break_on == '') then break end
end
break_on = nil
end
-- we shuld execute action/anti-action only if we check ALL conditions
local act
if last then act = pass and 'action' or 'anti-action' end
-- we shuld break
return act, not not break_on, matches
end
local function apply_match(s, match)
return string.gsub(s, "%$(%d)", function(i)
return match[tonumber(i)] or ''
end)
end
local function group_to_bridge(actions, group, fields)
local action, do_break, matches = check_conditions(group, fields)
if action then
local t = (action == 'action') and group.actions or group.anti_actions
for _, element in ipairs(t) do
local value = element.data
if element.type == 'export' and string.sub(value, 1, 8) == 'nolocal:' then
value = string.sub(value, 9)
end
-- we only support action/export
if element.type == 'export' or element.type == 'set' then
value = apply_match(value, matches)
append(actions, value)
end
if element.type == 'bridge' then
actions.bridge = apply_match(value, matches)
break
end
end
end
return do_break
end
local function extension_to_bridge(extension, actions, fields)
for _, group in order_keys(extension) do
local do_break = group_to_bridge(actions, group, fields)
if do_break then break end
end
end
local function self_test()
pcre_self_test()
local test_conditions = {
{
{conditions={
{type='destination_number', data='100', break_on=''};
}};
{destination_number = 100};
{'action', false};
};
{
{conditions={
{type='destination_number', data='100', break_on='on-true'};
}};
{destination_number = 100};
{'action', true};
};
{
{conditions={
{type='destination_number', data='101', break_on=''};
}};
{destination_number = 100};
{'anti-action', true};
};
{
{conditions={
{type='destination_number', data='100', break_on=''};
{type='destination_number', data='101', break_on=''};
{type='destination_number', data='102', break_on=''};
}};
{destination_number = 100};
{nil, true};
};
{
{conditions={
{type='destination_number', data='100', break_on='never'};
{type='destination_number', data='101', break_on='never'};
{type='destination_number', data='102', break_on='never'};
}};
{destination_number = 102};
{'action', false};
};
{
{conditions={
{type='destination_number', data='100', break_on='never'};
{type='destination_number', data='101', break_on='never'};
{type='destination_number', data='102', break_on='never'};
}};
{destination_number = 103};
{'anti-action', false};
};
{
{conditions={
{type='destination_number', data='100', break_on=''};
{type='destination_number', data='101', break_on=''};
{type='destination_number', data='102', break_on=''};
}};
{destination_number = 102};
{nil, true};
};
{
{conditions={
{type='', data='', break_on=''};
}};
{};
{'action', false};
};
{
{conditions={
{type='caller_id_number', data='123456', break_on=''};
}};
{};
{'anti-action', true};
};
}
for i, test in ipairs(test_conditions) do
io.write('Test conditions #' .. i)
local group, fields, result = test[1], test[2], test[3]
local action, do_break, matches = check_conditions(group, fields)
assert(action == result[1], tostring(action))
assert(do_break == result[2])
io.write(' - ok\n')
end
end
local function outbound_route_to_bridge(dbh, domain_uuid, fields)
local actions, dial_string = {}
dbh:query(select_routes_sql:format(domain_uuid), function(route)
local extension = {}
dbh:query(select_extensions_sql:format(route.dialplan_uuid), function(ext)
local group_no = tonumber(ext.dialplan_detail_group)
local tag = ext.dialplan_detail_tag
local element = {
type = ext.dialplan_detail_type;
data = ext.dialplan_detail_data;
break_on = ext.dialplan_detail_break;
inline = ext.dialplan_detail_inline;
}
local group = extension[ group_no ] or {
conditions = {};
actions = {};
anti_actions = {};
}
extension[ group_no ] = group
if tag == 'condition' then append(group.conditions, element) end
if tag == 'action' then append(group.actions, element) end
if tag == 'anti-action' then append(group.anti_actions, element) end
end)
local n = #actions
extension_to_bridge(extension, actions, fields)
if actions.bridge or (n > #actions and route.dialplan_continue == 'false') then
return 1
end
end)
if actions.bridge then return actions end
end
return setmetatable({
__self_test = self_test;
}, {__call = function(_, ...)
return outbound_route_to_bridge(...)
end})