Add. Use `route_to_bridge` module to build routes for ring groups. (#2907)

* Add. Use `route_to_bridge` module to build routes fro ring groups.

This commit has several improvements
1. Select only needed fields. (do not select quite big XML text strings)
2. Filter routes also by context name
3. Filter dialplans also by hostname
4. Handle conditions based not only `destination_number`
5. Handle `break` and `continue` attributes for extensions
6. Escape vars inside dial-string
7. Add log messages similar as FS dialplan do

* Add. `route_to_bridge` set inline vars so it possible use then in next conditions.
Add. `route_to_bridge` can execute basic api commands from allowed lists.
`route_to_bridge` expand all known vars. If var is unknown then it pass as is.
Fix. `export nolocal:` action.

* Fix. Short variable names

* Add. some comments

* Fix. Do not try execute empty string

This produce error messages `[ERR] switch_cpp.cpp:759 No application specified`

* Fix. Export nolocal values.
This commit is contained in:
Alexey Melnichuk 2017-11-17 17:15:47 +03:00 committed by FusionPBX
parent 0ccbad7d53
commit b0422af3e2
2 changed files with 515 additions and 163 deletions

View File

@ -30,6 +30,9 @@
--include the log --include the log
local log = require "resources.functions.log".ring_group local log = require "resources.functions.log".ring_group
-- include libs
local route_to_bridge = require "resources.functions.route_to_bridge"
--connect to the database --connect to the database
local Database = require "resources.functions.database"; local Database = require "resources.functions.database";
dbh = Database.new('system'); dbh = Database.new('system');
@ -118,6 +121,7 @@
uuid = session:getVariable("uuid"); uuid = session:getVariable("uuid");
context = session:getVariable("context"); context = session:getVariable("context");
call_direction = session:getVariable("call_direction"); call_direction = session:getVariable("call_direction");
accountcode = session:getVariable("accountcode");
end end
--default to local if nil --default to local if nil
@ -170,6 +174,9 @@
-- error(); -- error();
--end --end
--get current switchname
hostname = trim(api:execute("switchname", ""))
--get the ring group --get the ring group
ring_group_forward_enabled = ""; ring_group_forward_enabled = "";
ring_group_forward_destination = ""; ring_group_forward_destination = "";
@ -187,7 +194,7 @@
missed_call_app = row["ring_group_missed_call_app"]; missed_call_app = row["ring_group_missed_call_app"];
missed_call_data = row["ring_group_missed_call_data"]; missed_call_data = row["ring_group_missed_call_data"];
end); end);
--get the ring group user --get the ring group user
sql = "SELECT r.*, u.user_uuid FROM v_ring_groups as r, v_ring_group_users as u "; sql = "SELECT r.*, u.user_uuid FROM v_ring_groups as r, v_ring_group_users as u ";
sql = sql .. "where r.ring_group_uuid = :ring_group_uuid "; sql = sql .. "where r.ring_group_uuid = :ring_group_uuid ";
@ -304,7 +311,7 @@
--process the ring group --process the ring group
if (ring_group_forward_enabled == "true" and string.len(ring_group_forward_destination) > 0) then if (ring_group_forward_enabled == "true" and string.len(ring_group_forward_destination) > 0) then
--forward the ring group --forward the ring group
session:setVariable("toll_allow",ring_group_forward_toll_allow); session:setVariable("toll_allow",ring_group_forward_toll_allow);
session:execute("transfer", ring_group_forward_destination.." XML "..context); session:execute("transfer", ring_group_forward_destination.." XML "..context);
else else
--get the strategy of the ring group, if random, we use random() to order the destinations --get the strategy of the ring group, if random, we use random() to order the destinations
@ -422,33 +429,9 @@
--get the dialplan data and save it to a table --get the dialplan data and save it to a table
if (external) then if (external) then
sql = [[select * from v_dialplans as d, v_dialplan_details as s dialplans = route_to_bridge.preload_dialplan(
where (d.domain_uuid = :domain_uuid or d.domain_uuid is null) dbh, domain_uuid, {hostname = hostname, context = context}
and d.app_uuid = '8c914ec3-9fc0-8ab5-4cda-6c9288bdc9a3' )
and d.dialplan_enabled = 'true'
and d.dialplan_uuid = s.dialplan_uuid
order by
d.dialplan_order asc,
d.dialplan_name asc,
d.dialplan_uuid asc,
s.dialplan_detail_group asc,
CASE s.dialplan_detail_tag
WHEN 'condition' THEN 1
WHEN 'action' THEN 2
WHEN 'anti-action' THEN 3
ELSE 100 END,
s.dialplan_detail_order asc
]];
params = {domain_uuid = domain_uuid};
if debug["sql"] then
freeswitch.consoleLog("notice", "[ring group] SQL:" .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dialplans = {};
x = 1;
assert(dbh:query(sql, params, function(row)
dialplans[x] = row;
x = x + 1;
end));
end end
--process the destinations --process the destinations
@ -580,77 +563,65 @@
extension_uuid = trim(api:executeString(cmd)); extension_uuid = trim(api:executeString(cmd));
--send to user --send to user
local dial_string_to_user = "[sip_invite_domain="..domain_name..",call_direction="..call_direction..","..group_confirm.."leg_timeout="..destination_timeout..","..delay_name.."="..destination_delay..",dialed_extension=" .. row.destination_number .. ",extension_uuid="..extension_uuid .. row.record_session .. "]user/" .. row.destination_number .. "@" .. domain_name; local dial_string_to_user = "[sip_invite_domain="..domain_name..",call_direction="..call_direction..","..group_confirm.."leg_timeout="..destination_timeout..","..delay_name.."="..destination_delay..",dialed_extension=" .. row.destination_number .. ",extension_uuid="..extension_uuid .. row.record_session .. "]user/" .. row.destination_number .. "@" .. domain_name;
dial_string = dial_string_to_user; dial_string = dial_string_to_user;
elseif (tonumber(destination_number) == nil) then elseif (tonumber(destination_number) == nil) then
--sip uri --sip uri
dial_string = "[sip_invite_domain="..domain_name..",call_direction="..call_direction..","..group_confirm.."leg_timeout="..destination_timeout..","..delay_name.."="..destination_delay.."]" .. row.destination_number; dial_string = "[sip_invite_domain="..domain_name..",call_direction="..call_direction..","..group_confirm.."leg_timeout="..destination_timeout..","..delay_name.."="..destination_delay.."]" .. row.destination_number;
else else
--external number --external number
y = 0; dial_string = nil
dial_string = '';
previous_dialplan_uuid = ''; local session_mt = {__index = function(_, k) return session:getVariable(k) end}
regex_match = false; local params = setmetatable({
for k, r in pairs(dialplans) do __api__ = api,
if (y > 0) then destination_number = destination_number,
if (previous_dialplan_uuid ~= r.dialplan_uuid) then user_exists = 'false',
regex_match = false; call_direction = 'outbound',
bridge_match = false; domain_name = domain_name,
square = square .. "]"; domain_uuid = domain_uuid,
y = 0; destination_timeout = destination_timeout,
destination_delay = destination_delay,
}, session_mt)
local confirm = string.gsub(group_confirm, ',$', '') -- remove `,` from end of string
local route = route_to_bridge.apply_vars({ -- predefined actions
"domain_name=${domain_name}",
"domain_uuid=${domain_uuid}",
"sip_invite_domain=${domain_name}",
"leg_timeout=${destination_timeout}",
delay_name .. "=${destination_delay}",
"ignore_early_media=true",
confirm,
}, params)
route = route_to_bridge(dialplans, domain_uuid, params, route)
if route and route.bridge then
local remove_actions = {
["effective_caller_id_name="] = true;
["effective_caller_id_number="] = true;
['sip_h_X-accountcode='] = true;
}
-- cleanup variables
local i = 1 while i < #route do
-- remove vars from prev variant
if remove_actions[ route[i] ] then
table.remove(route, i)
i = i - 1
-- remove vars with unresolved vars
elseif string.find(route[i], '%${.+}') then
table.remove(route, i)
i = i - 1
-- remove vars with empty values
elseif string.find(route[i], '=$') then
table.remove(route, i)
i = i - 1
end end
i = i + 1
end end
if (r.dialplan_detail_tag == "condition") then dial_string = '[' .. table.concat(route, ',') .. ']' .. route.bridge
if (r.dialplan_detail_type == "destination_number") then
dial_string = "regex m:~"..destination_number.."~"..r.dialplan_detail_data
if (api:execute("regex", "m:~"..destination_number.."~"..r.dialplan_detail_data) == "true") then
--get the regex result
destination_result = trim(api:execute("regex", "m:~"..destination_number.."~"..r.dialplan_detail_data.."~$1"));
--set match equal to true
regex_match = true;
end
end
end
--regex_match = true;
--dial_string = r.dialplan_detail_data;
if (r.dialplan_detail_tag == "action") then
if (regex_match) then
--dial_string = 'match';
--replace $1
dialplan_detail_data = r.dialplan_detail_data:gsub("$1", destination_result);
--if the session is set then process the actions
if (y == 0) then
square = "[domain_name="..domain_name..",domain_uuid="..domain_uuid..",sip_invite_domain="..domain_name..",call_direction=outbound,"..group_confirm.."leg_timeout="..destination_timeout..","..delay_name.."="..destination_delay..",ignore_early_media=true,";
end
if (r.dialplan_detail_type == "set") then
--session:execute("eval", dialplan_detail_data);
if (dialplan_detail_data == "sip_h_X-accountcode=${accountcode}") then
if (session) then
accountcode = session:getVariable("accountcode");
if (accountcode) then
square = square .. "sip_h_X-accountcode="..accountcode..",";
end
end
elseif (dialplan_detail_data == "effective_caller_id_name=${outbound_caller_id_name}") then
elseif (dialplan_detail_data == "effective_caller_id_number=${outbound_caller_id_number}") then
else
square = square .. dialplan_detail_data..",";
end
elseif (r.dialplan_detail_type == "bridge") then
if (bridge_match) then
dial_string = dial_string .. delimiter .. square .."]"..dialplan_detail_data;
square = "[";
else
dial_string = square .."]"..dialplan_detail_data;
end
bridge_match = true;
break;
end
--increment the value
y = y + 1;
end
end
previous_dialplan_uuid = r.dialplan_uuid;
end end
end end
@ -776,12 +747,16 @@
or session:getVariable("originate_disposition") == "failure" or session:getVariable("originate_disposition") == "failure"
) then ) then
--execute the time out action --execute the time out action
session:execute(ring_group_timeout_app, ring_group_timeout_data); if ring_group_timeout_app and #ring_group_timeout_app > 0 then
session:execute(ring_group_timeout_app, ring_group_timeout_data);
end
end end
else else
if (ring_group_timeout_app ~= nil) then if (ring_group_timeout_app ~= nil) then
--execute the time out action --execute the time out action
session:execute(ring_group_timeout_app, ring_group_timeout_data); if ring_group_timeout_app and #ring_group_timeout_app > 0 then
session:execute(ring_group_timeout_app, ring_group_timeout_data);
end
else else
local sql = "SELECT ring_group_timeout_app, ring_group_timeout_data FROM v_ring_groups "; local sql = "SELECT ring_group_timeout_app, ring_group_timeout_data FROM v_ring_groups ";
sql = sql .. "where ring_group_uuid = :ring_group_uuid"; sql = sql .. "where ring_group_uuid = :ring_group_uuid";
@ -791,7 +766,9 @@
end end
dbh:query(sql, params, function(row) dbh:query(sql, params, function(row)
--execute the time out action --execute the time out action
session:execute(row.ring_group_timeout_app, row.ring_group_timeout_data); if row.ring_group_timeout_app and #row.ring_group_timeout_app > 0 then
session:execute(row.ring_group_timeout_app, row.ring_group_timeout_data);
end
end); end);
end end
end end

View File

@ -1,4 +1,9 @@
local log = require "resources.functions.log".route_to_bridge local log = require "resources.functions.log".route_to_bridge
require "resources.functions.split"
local allows_functions = {
['user_data'] = true,
}
local pcre_match local pcre_match
@ -48,20 +53,35 @@ local function pcre_self_test()
io.write(' - ok\n') io.write(' - ok\n')
end end
local select_routes_sql = [[ local select_outbound_dialplan_sql = [[
select * SELECT
from v_dialplans d.dialplan_uuid,
where (domain_uuid = :domain_uuid or domain_uuid is null) d.dialplan_context,
and (hostname = :hostname or hostname is null) d.dialplan_continue,
and app_uuid = '8c914ec3-9fc0-8ab5-4cda-6c9288bdc9a3' s.dialplan_detail_group,
and dialplan_enabled = 'true' s.dialplan_detail_break,
order by dialplan_order asc s.dialplan_detail_data,
]] s.dialplan_detail_inline,
s.dialplan_detail_tag,
local select_extensions_sql = [[ s.dialplan_detail_type
select * from v_dialplan_details FROM v_dialplans as d, v_dialplan_details as s
where dialplan_uuid = :dialplan_uuid WHERE (d.domain_uuid = :domain_uuid OR d.domain_uuid IS NULL)
order by dialplan_detail_group asc, dialplan_detail_order asc AND (d.hostname = :hostname OR d.hostname IS NULL)
AND d.app_uuid = '8c914ec3-9fc0-8ab5-4cda-6c9288bdc9a3'
AND d.dialplan_enabled = 'true'
AND d.dialplan_uuid = s.dialplan_uuid
ORDER BY
d.dialplan_order ASC,
d.dialplan_name ASC,
d.dialplan_uuid ASC,
s.dialplan_detail_group ASC,
CASE s.dialplan_detail_tag
WHEN 'condition' THEN 1
WHEN 'action' THEN 2
WHEN 'anti-action' THEN 3
ELSE 100
END,
s.dialplan_detail_order ASC
]] ]]
local function append(t, v) local function append(t, v)
@ -87,6 +107,11 @@ local function check_conditions(group, fields)
last = (n == #group.conditions) last = (n == #group.conditions)
local value = fields[condition.type] local value = fields[condition.type]
if (not value) and (condition_type ~= '') then -- try var name
local condition_type = string.match(condition.type, '^%${(.*)}$')
if condition_type then value = fields[condition_type] end
end
if (not value) and (condition.type ~= '') then -- skip unkonw fields if (not value) and (condition.type ~= '') then -- skip unkonw fields
log.errf('Unsupportded condition: %s', condition.type) log.errf('Unsupportded condition: %s', condition.type)
matches, pass = {}, false matches, pass = {}, false
@ -101,20 +126,20 @@ local function check_conditions(group, fields)
end end
break_on = condition.break_on break_on = condition.break_on
if break_on == 'always' then break end if break_on == 'always' then break
if break_on ~= 'never' then elseif break_on ~= 'never' then
if pass and break_on == 'on-true' then break end if pass then if break_on == 'on-true' then break end
if not pass and (break_on == 'on-false' or break_on == '') then break end elseif break_on == 'on-false' or break_on == '' then break end
end end
break_on = nil break_on = nil
end end
-- we shuld execute action/anti-action only if we check ALL conditions -- we should execute action/anti-action only if we check ALL conditions
local act local act
if last then act = pass and 'action' or 'anti-action' end if last then act = pass and 'action' or 'anti-action' end
-- we shuld break -- we should break
return act, not not break_on, matches return act, not not break_on, matches
end end
@ -124,23 +149,67 @@ local function apply_match(s, match)
end) end)
end end
local function apply_var(s, fields)
local str = string.gsub(s, "%$?%${([^$%(%){}= ]-)}", function(var)
return fields[var]
end)
if fields.__api__ then
local api = fields.__api__
-- try call functions like ('set result=${user_data(args)}')
str = string.gsub(str, "%${([^$%(%){}= ]+)%s*%((.-)%)%s*}", function(fn, par)
if allows_functions[fn] then
return api:execute(fn, par) or ''
end
log.warningf('try call not allowed function %s', tostring(fn))
end)
-- try call functions like 'set result=${user_data args}'
str = string.gsub(str, "%${([^$%(%){}= ]+)%s+(%S.-)%s*}", function(fn, par)
if allows_functions[fn] then
return api:execute(fn, par) or ''
end
log.warningf('try call not allowed function %s', tostring(fn))
end)
end
if string.find(str, '%${.+}') then
log.warningf('can not resolve vars inside `%s`', tostring(str))
end
return str
end
local function group_to_bridge(actions, group, fields) local function group_to_bridge(actions, group, fields)
local action, do_break, matches = check_conditions(group, fields) local action_type, do_break, matches = check_conditions(group, fields)
if action then if action_type then
local t = (action == 'action') and group.actions or group.anti_actions local t = (action_type == 'action') and group.actions or group.anti_actions
for _, element in ipairs(t) do for _, action in ipairs(t) do
local value = element.data local value = action.data
if element.type == 'export' and string.sub(value, 1, 8) == 'nolocal:' then
value = string.sub(value, 9) -- we only support set/export actions
if action.type == 'export' or action.type == 'set' then
local key
key, value = split_first(value, '=', true)
if key then
local bleg_only = (action.type == 'export') and (string.sub(key, 1, 8) == 'nolocal:')
if bleg_only then key = string.sub(key, 9) end
value = apply_match(value, matches)
value = apply_var(value, fields)
if action.inline and not bleg_only then
fields[key] = value
end
--! @todo do value escape?
append(actions, key .. '=' .. value)
end
end end
-- we only support action/export if action.type == 'bridge' then
if element.type == 'export' or element.type == 'set' then
value = apply_match(value, matches) value = apply_match(value, matches)
append(actions, value) value = apply_var(value, fields)
end
if element.type == 'bridge' then
actions.bridge = apply_match(value, matches) actions.bridge = apply_match(value, matches)
break break
end end
@ -158,6 +227,69 @@ local function extension_to_bridge(extension, actions, fields)
end end
local function self_test() local function self_test()
local unpack = unpack or table.unpack
local function assert_equal(expected, actions)
for i = 1, math.max(#expected, #actions) do
local e, v, msg = expected[i], actions[i]
if not e then
msg = string.format("unexpected value #%d - `%s`", i, v)
elseif not v then
msg = string.format("expected value `%s` at position #%d, but got no value", e, i)
elseif e ~= v then
msg = string.format("expected value `%s` at position #%d but got: `%s`", e, i, v)
end
assert(not msg, msg)
end
for name, e in pairs(expected) do
local v, msg = actions[name]
if not v then
msg = string.format("%s expected as `%s`, but got no value", name, e)
elseif e ~= v then
msg = string.format("expected value for %s is `%s`, but got: `%s`", name, e, v)
end
assert(not msg, msg)
end
for name, v in pairs(actions) do
local e, msg = expected[name]
if not e then
msg = string.format("expected value %s = `%s`", name, v)
end
assert(not msg, msg)
end
end
local function test_grout_to_bridge(group, params, ret, expected)
local actions = {}
local result = group_to_bridge(actions, group, params)
if result ~= ret then
local msg = string.format('expected `%s` but got `%s`', tostring(ret), tostring(result))
assert(false, msg)
end
assert_equal(expected, actions)
end
-- mock for API
local function API(t)
local api = {
execute = function(self, cmd, args)
cmd = assert(t[cmd])
return cmd[args]
end;
}
return api
end
local old_log = log
log = {
errf = function() end;
warningf = function() end;
debugf = function() end;
}
pcre_self_test() pcre_self_test()
local test_conditions = { local test_conditions = {
@ -251,53 +383,296 @@ local function self_test()
assert(do_break == result[2]) assert(do_break == result[2])
io.write(' - ok\n') io.write(' - ok\n')
end end
local test_actions = {
{ -- should not touch unknown vars
{actions={
{type='set', data='a=${b}'}
};
conditions={{type='', data='', break_on='on-true'}};
},
{ -- parameters
},
{ -- result
'a=${b}'
}
},
{ -- should call execute command with braces
{actions={
{type='set', data='a=${user_data(a b c)}'}
};
conditions={{type='', data='', break_on='on-true'}};
},
{ -- parameters
__api__ = API{user_data={['a b c'] = 'value'}}
},
{ -- result
'a=value'
}
},
{ -- should call execute command with spaces
{actions={
{type='set', data='a=${user_data a b c }'}
};
conditions={{type='', data='', break_on='on-true'}};
},
{ -- parameters
__api__ = API{user_data={['a b c'] = 'value'}}
},
{ -- result
'a=value'
}
},
{ -- should not call not allowed function
{actions={
{type='set', data='a=${user_exists( a b c )}'}
};
conditions={{type='', data='', break_on='on-true'}};
},
{ -- parameters
__api__ = API{user_data={['a b c'] = 'value'}}
},
{ -- result
'a=${user_exists( a b c )}'
}
},
{ -- should set inline vars
{actions={
{type='set', data='a=hello', inline=true},
{type='set', data='b=${a}'},
};
conditions={{type='', data='', break_on='on-true'}};
},
{ -- parameters
__api__ = API{user_data={['a b c'] = 'value'}}
},
{ -- result
'a=hello',
'b=hello',
}
},
{ -- should not set not inline vars
{actions={
{type='set', data='a=hello'},
{type='set', data='b=${a}'},
};
conditions={{type='', data='', break_on='on-true'}};
},
{ -- parameters
__api__ = API{user_data={['a b c'] = 'value'}}
},
{ -- result
'a=hello',
'b=${a}',
}
},
{ -- should expand vars inside call
{actions={
{type='set', data='a=${user_data(${a}${b})}'},
};
conditions={{type='', data='', break_on='on-true'}};
},
{ -- parameters
__api__ = API{user_data={['helloworld'] = 'value'}},
a = 'hello',
b = 'world',
},
{ -- result
'a=value',
}
},
{ -- should export nolocal
{actions={
{type='export', data='a=nolocal:value', inline=true},
{type='export', data='b=${a}'},
};
conditions={{type='', data='', break_on='on-true'}};
},
{ -- parameters
},
{ -- result
'a=value',
'b=${a}',
}
},
{ -- should handle bridge as last action
{actions={
{type='bridge', data='sofia/gateway/${a}'},
{type='set', data='a=123', inline=true},
};
conditions={{type='', data='', break_on='on-true'}};
},
{ -- parameters
a='gw'
},
{ -- result
bridge = 'sofia/gateway/gw'
}
},
{ -- should ingnore `nolocal` for set
{actions={
{type='set', data='a=nolocal:123', inline=true},
{type='export', data='b=${a}'},
};
conditions={{type='', data='', break_on='on-true'}};
},
{ -- parameters
},
{ -- result
'a=nolocal:123';
'b=nolocal:123';
}
},
{ -- should ingnore unsupportded actions
{actions={
{type='ring_ready', data=''},
{type='answer', data=''},
};
conditions={{type='', data='', break_on='on-true'}};
},
{ -- parameters
},
{ -- result
}
},
}
for i, test_case in ipairs(test_actions) do
local group, params, expected = unpack(test_case)
io.write('Test execute #' .. i)
test_grout_to_bridge(group, params, true, expected)
io.write(' - ok\n')
end
log = old_log
end end
local function outbound_route_to_bridge(dbh, domain_uuid, fields) -- Returns array of set/export actions and bridge command.
local actions, dial_string = {} --
require "resources.functions.trim"; -- This function does not set any var to session.
local hostname = trim(api:execute("switchname", "")); --
-- @param dbh database connection
-- @param domain_uuid
-- @param fields list of avaliable channel variables.
-- if `context` provided then dialplan will be filtered by this var
-- `__api__` key can be used to pass freeswitch.API object for execute
-- some functions in actions (e.g. `s=${user_data ...}`)
-- @param actions optional list of predefined actions
-- @return array part of table will contain list of actions.
-- `bridge` key will contain bridge statement
local function outbound_route_to_bridge(dbh, domain_uuid, fields, actions)
actions = actions or {}
local params = {} local hostname = fields.hostname
dbh:query(select_routes_sql, {domain_uuid=domain_uuid,hostname=hostname}, function(route) if not hostname then
local extension = {} require "resources.functions.trim";
params.dialplan_uuid = route.dialplan_uuid hostname = trim(api:execute("switchname", ""))
dbh:query(select_extensions_sql, params, function(ext) end
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 { -- try filter by context
conditions = {}; local context = fields.context
actions = {}; if context == '' then context = nil end
anti_actions = {};
}
extension[ group_no ] = group
if tag == 'condition' then append(group.conditions, element) end local current_dialplan_uuid, extension
if tag == 'action' then append(group.actions, element) end dbh:query(select_outbound_dialplan_sql, {domain_uuid=domain_uuid, hostname=hostname}, function(route)
if tag == 'anti-action' then append(group.anti_actions, element) end if context and context ~= route.dialplan_context then
end) -- skip dialplan for wrong contexts
return
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 current_dialplan_uuid ~= route.dialplan_uuid then
if extension then
local n = #actions
extension_to_bridge(extension, actions, fields)
-- if we found bridge or add any action and there no continue flag
if actions.bridge or (n > #actions and route.dialplan_continue == 'false') then
extension = nil
return 1
end
end
extension = {}
current_dialplan_uuid = route.dialplan_uuid
end
local group_no = tonumber(route.dialplan_detail_group)
local tag = route.dialplan_detail_tag
local element = {
type = route.dialplan_detail_type;
data = route.dialplan_detail_data;
break_on = route.dialplan_detail_break;
inline = route.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) end)
if extension and next(extension) then
extension_to_bridge(extension, actions, fields)
end
if actions.bridge then return actions end if actions.bridge then return actions end
end end
local function apply_vars(actions, fields)
for i, action in ipairs(actions) do
actions[i] = apply_var(action, fields)
end
return actions
end
local function wrap_dbh(t)
local i = 0
return {query = function(self, sql, params, callback)
while true do
i = i + 1
local row = t[i]
if not row then break end
local result = callback(row)
if result == 1 then break end
end
end}
end
-- Load all extension for outbound routes and
-- returns object which can be used instead real DBH object to build
-- dialplan for specific destination_number
local function preload_dialplan(dbh, domain_uuid, fields)
local hostname = fields and fields.hostname
if not hostname then
require "resources.functions.trim";
hostname = trim(api:execute("switchname", ""))
end
-- try filter by context
local context = fields and fields.context
if context == '' then context = nil end
local dialplan = {}
dbh:query(select_outbound_dialplan_sql, {domain_uuid=domain_uuid, hostname=hostname}, function(route)
if context and context ~= route.dialplan_context then
-- skip dialplan for wrong contexts
return
end
dialplan[#dialplan + 1] = route
end)
return wrap_dbh(dialplan), dialplan
end
return setmetatable({ return setmetatable({
__self_test = self_test; __self_test = self_test;
apply_vars = apply_vars;
preload_dialplan = preload_dialplan;
}, {__call = function(_, ...) }, {__call = function(_, ...)
return outbound_route_to_bridge(...) return outbound_route_to_bridge(...)
end}) end})