1--	Part of FusionPBX
2--	Copyright (C) 2010-2018 Mark J Crane <markjcrane@fusionpbx.com>
3--	All rights reserved.
4--
5--	Redistribution and use in source and binary forms, with or without
6--	modification, are permitted provided that the following conditions are met:
7--
8--	1. Redistributions of source code must retain the above copyright notice,
9--	   this list of conditions and the following disclaimer.
10--
11--	2. Redistributions in binary form must reproduce the above copyright
12--	   notice, this list of conditions and the following disclaimer in the
13--	   documentation and/or other materials provided with the distribution.
14--
15--	THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16--	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17--	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
18--	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
19--	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20--	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21--	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22--	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23--	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24--	POSSIBILITY OF SUCH DAMAGE.
25--
26--	Contributor(s):
27--	Mark J Crane <markjcrane@fusionpbx.com>
28--	Luis Daniel Lucio Qurioz <dlucio@okay.com.mx>
29
30--include the log
31	log = require "resources.functions.log".ring_group
32
33--connect to the database
34	local Database = require "resources.functions.database";
35	dbh = Database.new('system');
36
37--include json library
38	local json
39	if (debug["sql"]) then
40		json = require "resources.functions.lunajson"
41	end
42
43--include functions
44	require "resources.functions.trim";
45	require "resources.functions.explode";
46	require "resources.functions.base64";
47	require "resources.functions.file_exists";
48	require "resources.functions.channel_utils"
49	require "resources.functions.format_ringback"
50
51--- include libs
52	local route_to_bridge = require "resources.functions.route_to_bridge"
53	local play_file   = require "resources.functions.play_file"
54
55--define the session hangup
56	function session_hangup_hook()
57
58		--send info to the log
59			--freeswitch.consoleLog("notice","[ring_groups] originate_disposition: " .. session:getVariable("originate_disposition") .. "\n");
60
61		--status
62			status = 'answered'
63
64		--run the missed called function
65			if (
66				session:getVariable("originate_disposition")  == "ALLOTTED_TIMEOUT"
67				or session:getVariable("originate_disposition") == "NO_ANSWER"
68				or session:getVariable("originate_disposition") == "NO_USER_RESPONSE"
69				or session:getVariable("originate_disposition") == "USER_NOT_REGISTERED"
70				or session:getVariable("originate_disposition") == "NORMAL_TEMPORARY_FAILURE"
71				or session:getVariable("originate_disposition") == "NO_ROUTE_DESTINATION"
72				or session:getVariable("originate_disposition") == "USER_BUSY"
73				or session:getVariable("originate_disposition") == "RECOVERY_ON_TIMER_EXPIRE"
74				or session:getVariable("originate_disposition") == "failure"
75				or session:getVariable("originate_disposition") == "ORIGINATOR_CANCEL"
76			) then
77				--set the status
78					status = 'missed'
79				--send missed call notification
80					missed();
81			end
82
83		--send the ring group event
84		    event = freeswitch.Event("CUSTOM", "RING_GROUPS");
85			event:addHeader("domain_uuid", domain_uuid);
86			event:addHeader("domain_name", domain_name);
87			event:addHeader("ring_group_uuid", ring_group_uuid);
88			event:addHeader("user_uuid", user_uuid);
89			event:addHeader("ring_group_name", ring_group_name);
90			event:addHeader("ring_group_extension", ring_group_extension);
91			event:addHeader("status", status);
92			event:addHeader("call_uuid", uuid);
93			event:addHeader("caller_id_name", caller_id_name);
94			event:addHeader("caller_id_number", caller_id_number);
95			event:fire();
96
97	end
98
99--define iterator function to iterate over key/value pairs in string
100	local function split_vars_pairs(str)
101		local last_pos = 1
102		return function()
103			-- end of string
104			if not str then return end
105
106			-- handle case when there exists comma after kv pair
107			local action, next_pos = string.match(str, "([^=]+=%b''),()", last_pos)
108			if not action then
109				action, next_pos = string.match(str, "([^=]+=[^'][^,]-),()", last_pos)
110				if not action then
111					action, next_pos = string.match(str, "([^=]+=),()", last_pos)
112				end
113			end
114			if action then
115				last_pos = next_pos
116				return action
117			end
118
119			-- last kv pair may not have comma after it
120			if last_pos < #str then
121				action = string.match(str, "([^=]+=%b'')$", last_pos)
122				if not action then
123					action = string.match(str, "([^=]+=[^,]-)$", last_pos)
124				end
125				str = nil -- end of iteration
126			end
127
128			return action
129		end
130	end
131
132--set the hangup hook function
133	if (session:ready()) then
134		session:setHangupHook("session_hangup_hook");
135	end
136
137--get the variables
138	if (session:ready()) then
139		session:setAutoHangup(false);
140		domain_name = session:getVariable("domain_name");
141		domain_uuid = session:getVariable("domain_uuid");
142		ring_group_uuid = session:getVariable("ring_group_uuid");
143		recordings_dir = session:getVariable("recordings_dir");
144		sounds_dir = session:getVariable("sounds_dir");
145		username = session:getVariable("username");
146		dialplan = session:getVariable("dialplan");
147		caller_id_name = session:getVariable("caller_id_name");
148		caller_id_number = session:getVariable("caller_id_number");
149		network_addr = session:getVariable("network_addr");
150		ani = session:getVariable("ani");
151		aniii = session:getVariable("aniii");
152		rdnis = session:getVariable("rdnis");
153		destination_number = session:getVariable("destination_number");
154		source = session:getVariable("source");
155		uuid = session:getVariable("uuid");
156		context = session:getVariable("context");
157		call_direction = session:getVariable("call_direction");
158		accountcode = session:getVariable("accountcode");
159	end
160
161--default to local if nil
162	if (call_direction == nil) then
163		call_direction = "local";
164	end
165
166---set the call_timeout to a higher value to prevent the early timeout of the ring group
167	if (session:ready()) then
168		session:setVariable("call_timeout","300");
169	end
170
171--set ring ready
172	if (session:ready()) then
173		session:execute("ring_ready", "");
174	end
175
176--define additional variables
177	uuids = "";
178	external = "false";
179
180--set the sounds path for the language, dialect and voice
181	if (session:ready()) then
182		default_language = session:getVariable("default_language");
183		default_dialect = session:getVariable("default_dialect");
184		default_voice = session:getVariable("default_voice");
185		if (not default_language) then default_language = 'en'; end
186		if (not default_dialect) then default_dialect = 'us'; end
187		if (not default_voice) then default_voice = 'callie'; end
188	end
189
190--get record_ext
191	record_ext = session:getVariable("record_ext");
192	if (not record_ext) then
193		record_ext = "wav";
194	end
195
196--set the recording path
197	record_path = recordings_dir .. "/" .. domain_name .. "/archive/" .. os.date("%Y/%b/%d");
198	record_path = record_path:gsub("\\", "/");
199
200--set the recording file
201	record_name = uuid .. "." .. record_ext;
202
203--prepare the api object
204	api = freeswitch.API();
205
206--define the session hangup
207	--function on_hangup(s,status)
208	--	freeswitch.consoleLog("NOTICE","---- on_hangup: "..status.."\n");
209	--	error();
210	--end
211
212--get current switchname
213	hostname = trim(api:execute("switchname", ""))
214
215--get the domain_uuid if it not already set
216	if (domain_uuid == nil or domain_uuid == '' and domain_name) then
217		sql = "SELECT domain_uuid FROM v_domains as d ";
218		sql = sql .. "where d.domain_name = :domain_name ";
219		local params = {domain_name = domain_name};
220		status = dbh:query(sql, params, function(row)
221			domain_uuid = row["domain_uuid"];
222		end);
223	end
224
225--get the ring group
226	ring_group_forward_enabled = "";
227	ring_group_forward_destination = "";
228	sql = "SELECT r.* FROM v_ring_groups as r ";
229	sql = sql .. "where r.ring_group_uuid = :ring_group_uuid ";
230	sql = sql .. "and r.domain_uuid = :domain_uuid ";
231	local params = {ring_group_uuid = ring_group_uuid, domain_uuid = domain_uuid};
232	status = dbh:query(sql, params, function(row)
233		ring_group_name = row["ring_group_name"];
234		ring_group_extension = row["ring_group_extension"];
235		ring_group_greeting = row["ring_group_greeting"];
236		ring_group_forward_enabled = row["ring_group_forward_enabled"];
237		ring_group_forward_destination = row["ring_group_forward_destination"];
238		ring_group_forward_toll_allow = row["ring_group_forward_toll_allow"];
239		ring_group_caller_id_name = row["ring_group_caller_id_name"];
240		ring_group_caller_id_number = row["ring_group_caller_id_number"];
241		ring_group_cid_name_prefix = row["ring_group_cid_name_prefix"];
242		ring_group_cid_number_prefix = row["ring_group_cid_number_prefix"];
243		missed_call_app = row["ring_group_missed_call_app"];
244		missed_call_data = row["ring_group_missed_call_data"];
245	end);
246
247--play the greeting
248	if (session:ready()) then
249		if (ring_group_greeting and #ring_group_greeting > 0) then
250			session:sleep(1000);
251			play_file(dbh, domain_name, domain_uuid, ring_group_greeting)
252			session:sleep(1000);
253		end
254	end
255
256--get the ring group user
257	sql = "SELECT r.*, u.user_uuid FROM v_ring_groups as r, v_ring_group_users as u ";
258	sql = sql .. "where r.ring_group_uuid = :ring_group_uuid ";
259	sql = sql .. "and r.ring_group_uuid = u.ring_group_uuid ";
260	sql = sql .. "and r.domain_uuid = :domain_uuid ";
261	local params = {ring_group_uuid = ring_group_uuid, domain_uuid = domain_uuid};
262	status = dbh:query(sql, params, function(row)
263		user_uuid = row["user_uuid"];
264	end);
265
266--set the caller id
267	if (session:ready()) then
268		if (ring_group_cid_name_prefix ~= nil and string.len(ring_group_cid_name_prefix) > 0) then
269			session:execute("export", "effective_caller_id_name="..ring_group_cid_name_prefix.."#"..caller_id_name);
270		end
271		if (ring_group_cid_number_prefix ~= nil and string.len(ring_group_cid_number_prefix) > 0) then
272			session:execute("export", "effective_caller_id_number="..ring_group_cid_number_prefix..caller_id_number);
273		end
274	end
275
276--check the missed calls
277	function missed()
278
279		--send missed call email
280		if (missed_call_app ~= nil and missed_call_data ~= nil) then
281			if (missed_call_app == "email") then
282				--set the sounds path for the language, dialect and voice
283					default_language = session:getVariable("default_language");
284					default_dialect = session:getVariable("default_dialect");
285					default_voice = session:getVariable("default_voice");
286					if (not default_language) then default_language = 'en'; end
287					if (not default_dialect) then default_dialect = 'us'; end
288					if (not default_voice) then default_voice = 'callie'; end
289
290				--get the templates
291					local sql = "SELECT * FROM v_email_templates ";
292					sql = sql .. "WHERE (domain_uuid = :domain_uuid or domain_uuid is null) ";
293					sql = sql .. "AND template_language = :template_language ";
294					sql = sql .. "AND template_category = 'missed' "
295					sql = sql .. "AND template_enabled = 'true' "
296					sql = sql .. "ORDER BY domain_uuid DESC "
297					local params = {domain_uuid = domain_uuid, template_language = default_language.."-"..default_dialect};
298					if (debug["sql"]) then
299						freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
300					end
301					dbh:query(sql, params, function(row)
302						subject = row["template_subject"];
303						body = row["template_body"];
304					end);
305
306				--prepare the headers
307					headers = '{"X-FusionPBX-Domain-UUID":"'..domain_uuid..'",';
308					headers = headers..'"X-FusionPBX-Domain-Name":"'..domain_name..'",';
309					headers = headers..'"X-FusionPBX-Call-UUID":"'..uuid..'",';
310					headers = headers..'"X-FusionPBX-Email-Type":"missed"}';
311
312				--prepare the subject
313					subject = subject:gsub("${caller_id_name}", caller_id_name);
314					subject = subject:gsub("${caller_id_number}", caller_id_number);
315					subject = subject:gsub("${ring_group_name}", ring_group_name);
316					subject = subject:gsub("${ring_group_extension}", ring_group_extension);
317					subject = subject:gsub("${sip_to_user}", ring_group_name);
318					subject = subject:gsub("${dialed_user}", ring_group_extension);
319					subject = trim(subject);
320					subject = '=?utf-8?B?'..base64.encode(subject)..'?=';
321
322				--prepare the body
323					body = body:gsub("${caller_id_name}", caller_id_name);
324					body = body:gsub("${caller_id_number}", caller_id_number);
325					body = body:gsub("${ring_group_name}", ring_group_name);
326					body = body:gsub("${ring_group_extension}", ring_group_extension);
327					body = body:gsub("${sip_to_user}", ring_group_name);
328					body = body:gsub("${dialed_user}", ring_group_extension);
329					body = body:gsub(" ", "&nbsp;");
330					body = body:gsub("%s+", "");
331					body = body:gsub("&nbsp;", " ");
332					body = body:gsub("\n", "");
333					body = body:gsub("\n", "");
334					body = body:gsub("'", "&#39;");
335					body = body:gsub([["]], "&#34;");
336					body = trim(body);
337
338				--send the email
339					cmd = "luarun email.lua "..missed_call_data.." "..missed_call_data.." "..headers.." '"..subject.."' '"..body.."'";
340					if (debug["info"]) then
341						freeswitch.consoleLog("notice", "[missed call] cmd: " .. cmd .. "\n");
342					end
343					api = freeswitch.API();
344					result = api:executeString(cmd);
345			end
346		end
347	end
348
349--get the destination and follow the forward
350	function get_forward_all(count, destination_number, domain_name)
351		cmd = "user_exists id ".. destination_number .." "..domain_name;
352		freeswitch.consoleLog("notice", "[ring groups][call forward all] " .. cmd .. "\n");
353		user_exists = api:executeString(cmd);
354		if (user_exists == "true") then
355			---check to see if the new destination is forwarded - third forward
356				cmd = "user_data ".. destination_number .."@" ..domain_name.." var forward_all_enabled";
357				if (api:executeString(cmd) == "true") then
358					--get the toll_allow var
359						cmd = "user_data ".. destination_number .."@" ..leg_domain_name.." var toll_allow";
360						toll_allow = api:executeString(cmd);
361						freeswitch.consoleLog("notice", "[ring groups][call forward all] " .. destination_number .. " toll_allow is ".. toll_allow .."\n");
362
363					--get the new destination - third foward
364						cmd = "user_data ".. destination_number .."@" ..domain_name.." var forward_all_destination";
365						destination_number = api:executeString(cmd);
366						freeswitch.consoleLog("notice", "[ring groups][call forward all] " .. count .. " " .. cmd .. " ".. destination_number .."\n");
367						count = count + 1;
368						if (count < 5) then
369							count, destination_number = get_forward_all(count, destination_number, domain_name);
370						end
371				end
372		end
373		return count, destination_number, toll_allow;
374	end
375
376--process the ring group
377	if (ring_group_forward_enabled == "true" and string.len(ring_group_forward_destination) > 0) then
378		--forward the ring group
379			session:setVariable("toll_allow",ring_group_forward_toll_allow);
380			session:execute("transfer", ring_group_forward_destination.." XML "..context);
381	else
382		--get the strategy of the ring group, if random, we use random() to order the destinations
383			local sql = [[
384				SELECT
385					r.ring_group_strategy
386				FROM
387					v_ring_groups as r
388				WHERE
389					ring_group_uuid = :ring_group_uuid
390					AND r.domain_uuid = :domain_uuid
391					AND r.ring_group_enabled = 'true'
392			]];
393
394			local params = {ring_group_uuid = ring_group_uuid, domain_uuid = domain_uuid};
395
396			assert(dbh:query(sql, params, function(row)
397				if (row.ring_group_strategy == "random") then
398					if (database["type"] == "mysql") then
399						sql_order = 'rand()'
400					else
401						sql_order = 'random()' --both postgresql and sqlite uses random() instead of rand()
402					end
403				else
404					sql_order='d.destination_delay, d.destination_number asc'
405				end
406			end));
407
408		--get the ring group destinations
409			sql = [[
410				SELECT
411					r.ring_group_strategy, r.ring_group_timeout_app, r.ring_group_distinctive_ring,
412					d.destination_number, d.destination_delay, d.destination_timeout, d.destination_prompt,
413					r.ring_group_caller_id_name, r.ring_group_caller_id_number,
414					r.ring_group_cid_name_prefix, r.ring_group_cid_number_prefix,
415					r.ring_group_timeout_data, r.ring_group_ringback
416				FROM
417					v_ring_groups as r, v_ring_group_destinations as d
418				WHERE
419					d.ring_group_uuid = r.ring_group_uuid
420					AND d.ring_group_uuid = :ring_group_uuid
421					AND r.domain_uuid = :domain_uuid
422					AND r.ring_group_enabled = 'true'
423				ORDER BY
424					]]..sql_order..[[
425			]];
426			if debug["sql"] then
427				freeswitch.consoleLog("notice", "[ring group] SQL:" .. sql .. "; params:" .. json.encode(params) .. "\n");
428			end
429			destinations = {};
430			x = 1;
431			destination_count = 0;
432			assert(dbh:query(sql, params, function(row)
433				if (row.destination_prompt == "1" or row.destination_prompt == "2") then
434					prompt = "true";
435				end
436
437				local array = explode("@",row.destination_number);
438				if (array[2] == nil) then
439					-- no @
440					leg_domain_name = domain_name;
441				else
442					leg_domain_name = array[2];
443				end
444
445				--follow the forwards
446				count, destination_number, toll_allow = get_forward_all(0, row.destination_number, leg_domain_name);
447
448				--update values
449				row['destination_number'] = destination_number
450				row['toll_allow'] = toll_allow;
451
452				--check if the user exists
453				cmd = "user_exists id ".. destination_number .." "..domain_name;
454				user_exists = api:executeString(cmd);
455
456				--cmd = "user_exists id ".. destination_number .." "..leg_domain_name;
457				if (user_exists == "true") then
458					--add user_exists true or false to the row array
459						row['user_exists'] = "true";
460					--handle do_not_disturb
461						cmd = "user_data ".. destination_number .."@" ..leg_domain_name.." var do_not_disturb";
462						if (api:executeString(cmd) ~= "true") then
463							--add the row to the destinations array
464							destinations[x] = row;
465						end
466				else
467					--set the values
468						external = "true";
469						row['user_exists'] = "false";
470					--add the row to the destinations array
471						destinations[x] = row;
472				end
473				row['domain_name'] = leg_domain_name;
474				destination_count = destination_count + 1;
475				x = x + 1;
476			end));
477			--freeswitch.consoleLog("NOTICE", "[ring_group] external "..external.."\n");
478
479		--get the dialplan data and save it to a table
480			if (external == "true") then
481				dialplans = route_to_bridge.preload_dialplan(
482					dbh, domain_uuid, {hostname = hostname, context = context}
483				)
484			end
485
486		--process the destinations
487			x = 0;
488			for key, row in pairs(destinations) do
489				--set the values from the database as variables
490					user_exists = row.user_exists;
491					ring_group_strategy = row.ring_group_strategy;
492					ring_group_timeout_app = row.ring_group_timeout_app;
493					ring_group_timeout_data = row.ring_group_timeout_data;
494					ring_group_caller_id_name = row.ring_group_caller_id_name;
495					ring_group_caller_id_number = row.ring_group_caller_id_number;
496					ring_group_cid_name_prefix = row.ring_group_cid_name_prefix;
497					ring_group_cid_number_prefix = row.ring_group_cid_number_prefix;
498					ring_group_distinctive_ring = row.ring_group_distinctive_ring;
499					ring_group_ringback = row.ring_group_ringback;
500					destination_number = row.destination_number;
501					destination_delay = row.destination_delay;
502					destination_timeout = row.destination_timeout;
503					destination_prompt = row.destination_prompt;
504					domain_name = row.domain_name;
505					toll_allow = row.toll_allow;
506
507				--determine if the user is registered if not registered then lookup
508					cmd = "sofia_contact */".. destination_number .."@" ..domain_name;
509					if (api:executeString(cmd) == "error/user_not_registered") then
510freeswitch.consoleLog("NOTICE", "[ring_group] "..cmd.."\n");
511						cmd = "user_data ".. destination_number .."@" ..domain_name.." var forward_user_not_registered_enabled";
512freeswitch.consoleLog("NOTICE", "[ring_group] "..cmd.."\n");
513						if (api:executeString(cmd) == "true") then
514							--get the new destination number
515							cmd = "user_data ".. destination_number .."@" ..domain_name.." var forward_user_not_registered_destination";
516freeswitch.consoleLog("NOTICE", "[ring_group] "..cmd.."\n");
517							not_registered_destination_number = api:executeString(cmd);
518freeswitch.consoleLog("NOTICE", "[ring_group] "..not_registered_destination_number.."\n");
519							if (not_registered_destination_number ~= nil) then
520								destination_number = not_registered_destination_number;
521							end
522
523							--check the new destination number for user_exists
524							cmd = "user_exists id ".. destination_number .." "..domain_name;
525							user_exists = api:executeString(cmd);
526							if (user_exists == "true") then
527								row['user_exists'] = "true";
528							else
529								row['user_exists'] = "false";
530							end
531						end
532					end
533
534				--follow the forwards
535					count, destination_number = get_forward_all(0, destination_number, leg_domain_name);
536
537				--check if the user exists
538					cmd = "user_exists id ".. destination_number .." "..domain_name;
539					user_exists = api:executeString(cmd);
540
541				--set ringback
542					ring_group_ringback = format_ringback(ring_group_ringback);
543					session:setVariable("ringback", ring_group_ringback);
544					session:setVariable("transfer_ringback", ring_group_ringback);
545
546				--set the timeout if there is only one destination
547					if (destination_count == 1) then
548						session:execute("set", "call_timeout="..row.destination_timeout);
549					end
550
551				--setup the delimiter
552					delimiter = ",";
553					if (ring_group_strategy == "rollover") then
554						delimiter = "|";
555					end
556					if (ring_group_strategy == "sequence") then
557						delimiter = "|";
558					end
559					if (ring_group_strategy == "random") then
560						delimiter = "|";
561					end
562					if (ring_group_strategy == "simultaneous") then
563						delimiter = ",";
564					end
565					if (ring_group_strategy == "enterprise") then
566						delimiter = ":_:";
567					end
568
569				--leg delay settings
570					if (ring_group_strategy == "enterprise") then
571						delay_name = "originate_delay_start";
572						destination_delay = destination_delay * 1000;
573					else
574						delay_name = "leg_delay_start";
575					end
576
577				--create a new uuid and add it to the uuid list
578					new_uuid = api:executeString("create_uuid");
579					if (string.len(uuids) == 0) then
580						uuids = new_uuid;
581					else
582						uuids = uuids ..",".. new_uuid;
583					end
584					session:execute("set", "uuids="..uuids);
585
586				--export the ringback
587					if (ring_group_distinctive_ring ~= nil) then
588						ring_group_distinctive_ring = ring_group_distinctive_ring:gsub("${local_ip_v4}", session:getVariable("local_ip_v4"));
589						ring_group_distinctive_ring = ring_group_distinctive_ring:gsub("${domain_name}", session:getVariable("domain_name"));
590						session:execute("export", "sip_h_Alert-Info="..ring_group_distinctive_ring);
591					end
592
593				--set confirm
594					if (ring_group_strategy == "simultaneous"
595						or ring_group_strategy == "sequence"
596						or ring_group_strategy == "rollover") then
597							session:execute("set", "group_confirm_key=exec");
598							session:execute("set", "group_confirm_file=lua ".. scripts_dir:gsub('\\','/') .."/confirm.lua");
599					end
600
601				--determine confirm prompt
602					if (destination_prompt == nil) then
603						group_confirm = "confirm=false,";
604					elseif (destination_prompt == "1") then
605						group_confirm = "group_confirm_key=exec,group_confirm_file=lua ".. scripts_dir:gsub('\\','/') .."/confirm.lua,confirm=true,";
606					elseif (destination_prompt == "2") then
607						group_confirm = "group_confirm_key=exec,group_confirm_file=lua ".. scripts_dir:gsub('\\','/') .."/confirm.lua,confirm=true,";
608					else
609						group_confirm = "confirm=false,";
610					end
611
612				--get user_record value and determine whether to record the session
613					cmd = "user_data ".. destination_number .."@"..domain_name.." var user_record";
614					user_record = trim(api:executeString(cmd));
615					--set the record_session variable
616					record_session = false;
617					if (user_record == "all") then
618						record_session = true;
619					end
620					if (user_record == "inbound" and call_direction == "inbound") then
621						record_session = true;
622					end
623					if (user_record == "outbound" and call_direction == "outbound") then
624						record_session = true;
625					end
626					if (user_record == "local" and call_direction == "local") then
627						record_session = true;
628					end
629
630				--record the session
631					if (record_session) then
632						record_session = ",api_on_answer='uuid_record "..uuid.." start ".. record_path .. "/" .. record_name .. "',record_path='".. record_path .."',record_name="..record_name;
633					else
634						record_session = ""
635					end
636					row.record_session = record_session
637
638				--process according to user_exists, sip_uri, external number
639					if (user_exists == "true") then
640						--get the extension_uuid
641						cmd = "user_data ".. destination_number .."@"..domain_name.." var extension_uuid";
642						extension_uuid = trim(api:executeString(cmd));
643						--send to user
644						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;
645						dial_string = dial_string_to_user;
646					elseif (tonumber(destination_number) == nil) then
647						--sip uri
648						dial_string = "[sip_invite_domain="..domain_name..",call_direction="..call_direction..","..group_confirm.."leg_timeout="..destination_timeout..","..delay_name.."="..destination_delay.."]" .. row.destination_number;
649					else
650						--external number or direct dial
651							dial_string = nil
652
653						--prepare default actions
654							local confirm = string.gsub(group_confirm, ',$', '') -- remove `,` from end of string
655							local route = { -- predefined actions
656								"domain_name=${domain_name}",
657								"domain_uuid=${domain_uuid}",
658								"sip_invite_domain=${domain_name}",
659								"call_direction=${call_direction}",
660								"leg_timeout=${destination_timeout}",
661								delay_name .. "=${destination_delay}",
662								"ignore_early_media=true",
663								confirm,
664							}
665
666						--prepare default variables
667							local session_mt = {__index = function(_, k) return session:getVariable(k) end}
668							local params = setmetatable({
669								__api__             = api,
670								destination_number  = destination_number,
671								user_exists         = 'false',
672								call_direction      = 'outbound',
673								domain_name         = domain_name,
674								domain_uuid         = domain_uuid,
675								destination_timeout = destination_timeout,
676								destination_delay   = destination_delay,
677								toll_allow			= toll_allow,
678							}, session_mt)
679
680						--find destination route
681							if (tonumber(destination_number) == nil) then
682								--user define direct destination like `[key=value]sofia/gateway/carrier/123456`
683									local variables, destination = string.match(destination_number, "^%[(.-)%](.+)$")
684									if not variables then
685										destination = destination_number
686									else
687										for action in split_vars_pairs(variables) do
688											route[#route + 1] = action
689										end
690									end
691									route = route_to_bridge.apply_vars(route, params)
692									route.bridge = destination
693							else
694								--user define external number as destination
695									route = route_to_bridge.apply_vars(route, params)
696									route = route_to_bridge(dialplans, domain_uuid, params, route)
697							end
698
699						--build the dial string
700							if route and route.bridge then
701								local remove_actions = {
702									["effective_caller_id_name="]   = true;
703									["effective_caller_id_number="] = true;
704									['sip_h_X-accountcode=']        = true;
705								}
706
707								-- cleanup variables
708								local i = 1 while i < #route do
709									-- remove vars from prev variant
710									if remove_actions[ route[i] ] then
711										table.remove(route, i)
712										i = i - 1
713									-- remove vars with unresolved vars
714									elseif string.find(route[i], '%${.+}') then
715										table.remove(route, i)
716										i = i - 1
717									-- remove vars with empty values
718									elseif string.find(route[i], '=$') then
719										table.remove(route, i)
720										i = i - 1
721									end
722									i = i + 1
723								end
724
725								--set the caller id
726								caller_id = '';
727								if (ring_group_caller_id_name ~= nil) then
728									caller_id = "origination_caller_id_name='"..ring_group_caller_id_name.."'"
729								end
730								if (ring_group_caller_id_number ~= nil) then
731									caller_id = caller_id .. ",origination_caller_id_number="..ring_group_caller_id_number..",";
732								end
733
734								--set the destination dial string
735								dial_string = '['.. caller_id .. table.concat(route, ',') .. ']' .. route.bridge
736							end
737					end
738
739				--add a delimiter between destinations
740					if (dial_string ~= nil) then
741						--freeswitch.consoleLog("notice", "[ring group] dial_string: " .. dial_string .. "\n");
742						if (x == 0) then
743							if (ring_group_strategy == "enterprise") then
744								app_data = dial_string;
745							else
746								app_data = "{ignore_early_media=true}"..dial_string;
747							end
748						else
749							if (app_data == nil) then
750								if (ring_group_strategy == "enterprise") then
751									app_data = dial_string;
752								else
753									app_data = "{ignore_early_media=true}"..dial_string;
754								end
755							else
756								app_data = app_data .. delimiter .. dial_string;
757							end
758						end
759					end
760
761				--increment the value of x
762					x = x + 1;
763			end
764
765		--session execute
766			if (session:ready()) then
767				--set the variables
768					session:execute("set", "hangup_after_bridge=true");
769					session:execute("set", "continue_on_fail=true");
770
771				-- support conf-xfer feature
772				-- do
773				-- 	local uuid = api:executeString("create_uuid")
774				-- 	session:execute("export", "conf_xfer_number=xfer-" .. uuid .. "-" .. domain_name)
775				-- end
776				--set bind digit action
777					local bind_target = 'peer'
778					if session:getVariable("sip_authorized") == "true" then
779						bind_target = 'both';
780					end
781					local bindings = {
782						"local,*2,exec:record_session," .. record_path .. "/" .. record_name,
783						-- "local,*0,exec:execute_extension,conf_xfer_from_dialplan XML conf-xfer@" .. context
784					}
785					for _, str in ipairs(bindings) do
786						session:execute("bind_digit_action", str .. "," .. bind_target)
787					end
788					session:execute("digit_action_set_realm", "local");
789
790					--if the user is busy rollover to the next destination
791						if (ring_group_strategy == "rollover") then
792							x = 0;
793							app_data = '{ignore_early_media=true}';
794							for key, row in pairs(destinations) do
795								--set the values from the database as variables
796									user_exists = row.user_exists;
797									destination_number = row.destination_number;
798									domain_name = row.domain_name;
799
800								--get the extension_uuid
801									if (user_exists == "true") then
802										cmd = "user_data ".. destination_number .."@"..domain_name.." var extension_uuid";
803										extension_uuid = trim(api:executeString(cmd));
804									end
805
806								--if the timeout was reached go to the timeout action
807									if (x > 0) then
808										if (session:getVariable("originate_disposition") == "ALLOTTED_TIMEOUT"
809											or session:getVariable("originate_disposition") == "NO_ANSWER"
810											or session:getVariable("originate_disposition") == "NO_USER_RESPONSE") then
811												break;
812										end
813									end
814
815								--send the call to the destination
816									if (user_exists == "true") then
817										dial_string = "["..group_confirm.."sip_invite_domain="..domain_name..",leg_timeout="..destination_timeout..",call_direction="..call_direction..",dialed_extension=" .. destination_number .. ",extension_uuid="..extension_uuid..",domain_name="..domain_name..",domain_uuid="..domain_uuid..row.record_session.."]user/" .. destination_number .. "@" .. domain_name;
818									elseif (tonumber(destination_number) == nil) then
819										dial_string = "["..group_confirm.."sip_invite_domain="..domain_name..",call_timeout="..destination_timeout..",call_direction=outbound,domain_name="..domain_name..",domain_uuid="..domain_uuid.."]" .. destination_number;
820									else
821										dial_string = "["..group_confirm.."sip_invite_domain="..domain_name..",call_timeout="..destination_timeout..",domain_name="..domain_name..",domain_uuid="..domain_uuid..",call_direction=outbound]loopback/" .. destination_number;
822									end
823
824								--add the delimiter
825									if (x == 0) then
826										app_data = app_data .. dial_string;
827									else
828										app_data = app_data .. delimiter .. dial_string;
829									end
830
831								--increment the value of x
832									x = x + 1;
833							end
834						end
835
836					--execute the bridge
837						if (app_data ~= nil) then
838							if (ring_group_strategy == "enterprise") then
839								app_data = app_data:gsub("%[", "{");
840								app_data = app_data:gsub("%]", "}");
841							end
842							freeswitch.consoleLog("NOTICE", "[ring group] app_data: "..app_data.."\n");
843							-- log.noticef("bridge begin: originate_disposition:%s answered:%s ready:%s bridged:%s", session:getVariable("originate_disposition"), session:answered() and "true" or "false", session:ready() and "true" or "false", session:bridged() and "true" or "false")
844							session:execute("bridge", app_data);
845							-- log.noticef("bridge done: originate_disposition:%s answered:%s ready:%s bridged:%s", session:getVariable("originate_disposition"), session:answered() and "true" or "false", session:ready() and "true" or "false", session:bridged() and "true" or "false")
846						end
847
848					--timeout destination
849						if (app_data ~= nil) then
850							if session:ready() and (
851								session:getVariable("originate_disposition")  == "ALLOTTED_TIMEOUT"
852								or session:getVariable("originate_disposition") == "NO_ANSWER"
853								or session:getVariable("originate_disposition") == "NO_USER_RESPONSE"
854								or session:getVariable("originate_disposition") == "USER_NOT_REGISTERED"
855								or session:getVariable("originate_disposition") == "NORMAL_TEMPORARY_FAILURE"
856								or session:getVariable("originate_disposition") == "NO_ROUTE_DESTINATION"
857								or session:getVariable("originate_disposition") == "USER_BUSY"
858								or session:getVariable("originate_disposition") == "RECOVERY_ON_TIMER_EXPIRE"
859								or session:getVariable("originate_disposition") == "failure"
860							) then
861								--execute the time out action
862									if ring_group_timeout_app and #ring_group_timeout_app > 0 then
863										session:execute(ring_group_timeout_app, ring_group_timeout_data);
864									end
865							end
866						else
867							if (ring_group_timeout_app ~= nil) then
868								--execute the time out action
869									if ring_group_timeout_app and #ring_group_timeout_app > 0 then
870										session:execute(ring_group_timeout_app, ring_group_timeout_data);
871									end
872							else
873								local sql = "SELECT ring_group_timeout_app, ring_group_timeout_data FROM v_ring_groups ";
874								sql = sql .. "where ring_group_uuid = :ring_group_uuid";
875								local params = {ring_group_uuid = ring_group_uuid};
876								if debug["sql"] then
877									freeswitch.consoleLog("notice", "[ring group] SQL:" .. sql .. "; params:" .. json.encode(params) .. "\n");
878								end
879								dbh:query(sql, params, function(row)
880									--execute the time out action
881										if row.ring_group_timeout_app and #row.ring_group_timeout_app > 0 then
882											session:execute(row.ring_group_timeout_app, row.ring_group_timeout_data);
883										end
884								end);
885							end
886						end
887				end
888
889		end
890
891--actions
892	--ACTIONS = {}
893	--table.insert(ACTIONS, {"set", "hangup_after_bridge=true"});
894	--table.insert(ACTIONS, {"set", "continue_on_fail=true"});
895	--table.insert(ACTIONS, {"bridge", app_data});
896	--table.insert(ACTIONS, {ring_group_timeout_app, ring_group_timeout_data});
897