root/contributed_modules/modules/smartroutes/functions.inc.php

Revision 11506, 55.8 kB (checked in by escape2mtns, 2 years ago)

Auto Check-in of any outstanding patches

  • Property svn:executable set to *
Line 
1 <?php   
2 /**
3  * FreePBX SmartRoutes Module
4  *
5  * Copyright (c) 2011, VoiceNation, LLC.
6  *
7  * This program is free software, distributed under the terms of
8  * the GNU General Public License Version 2.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU Affero General Public License for more details.
14  *
15  * You should have received a copy of the GNU Affero General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  *
18 */
19
20 include '../fax/functions.inc.php'// so we can include the same fax hook code that the standard inbound route includes (if available)
21
22 // return a list of smartroutes for menu/display
23 function smartroutes_list() {
24     global $db;
25
26     $sql = "SELECT * FROM smartroute ORDER BY name";
27     $list = $db->getAll($sql, DB_FETCHMODE_ASSOC);
28     if(DB::IsError($list)) return null;
29
30     return $list;   
31 }
32
33
34 // return the asterisk dialplan goto destination for a specific smartroute
35 function smartroutes_getdest($id) {
36     return array('smartroute-'.$id.',s,1');
37     }
38     
39     
40 // return the database record for a given smartroute
41 function smartroutes_get_route($id) {
42     global $db;
43     
44     $route = $db->getAll("SELECT * FROM smartroute where id='$id'", DB_FETCHMODE_ASSOC);
45     
46     if(DB::IsError($route)) {
47         return null;
48         }
49         
50     return $route[0];
51     }
52
53
54 // return the queries for a given route   
55 function smartroutes_get_queries($id) {
56     global $db;
57
58     $sql = "SELECT * FROM smartroute_query where id='$id'";
59     $queries = $db->getAll($sql, DB_FETCHMODE_ASSOC);   
60         
61     return $queries;
62     }
63
64     
65 // return the destinations for a given route   
66 function smartroutes_get_dests($id) {
67     global $db;
68
69     $sql = "SELECT * FROM smartroute_dest where id='$id'";
70     $dests = $db->getAll($sql, DB_FETCHMODE_ASSOC);   
71         
72     return $dests;
73     }
74     
75     
76 // return the name of the smartroute marked as trunk default   
77 function smartroutes_get_trunkdefault() {
78     global $db;
79     
80     $route = $db->getRow("SELECT name FROM smartroute where trunkdefault=1");
81     
82     if(DB::IsError($route) || count($route) == 0) {
83         return null;
84         }
85         
86     if(!empty($route[0]) && !DB::IsError($route) && isset($route[0])) return $route[0];
87     
88     return null;
89     }
90
91     
92 // return the dialplan destination for the smartroute marked as trunk default   
93 function smartroutes_get_trunkdefault_gotoroute() {
94     global $db;
95     
96     $route = $db->getRow("SELECT id FROM smartroute where trunkdefault=1");
97     
98     if(DB::IsError($route) || count($route) == 0) {
99         return null;
100         }
101         
102     if(!empty($route[0]) && !DB::IsError($route) && isset($route[0])) return smartroutes_getdest($route[0]);
103     
104     return null;
105     }
106     
107
108 // return a list of the Asterisk odbc resources defined   
109 function smartroutes_get_dsns() {
110     $ret_dsns = array();
111     
112     $asterisk_dsns = smartroutes_read_config('/etc/asterisk/res_odbc.conf');   
113     if(count($asterisk_dsns)) {
114         $ret_dsns = array_keys($asterisk_dsns);
115         }
116     return $ret_dsns;
117     }
118     
119
120 // add a smartroute entry   
121 function smartroutes_add_route($name) {
122     global $db;
123     
124     // see if that route already exists
125     $route = $db->getRow("SELECT id FROM smartroute WHERE name='$name'");
126     
127     // route doesn't exist so create
128     if(DB::IsError($route) || count($route)==0) {
129         sql("INSERT INTO smartroute (name) VALUES ('$name')");
130         $route = $db->getRow("SELECT id FROM smartroute WHERE name='$name'");
131         sql("INSERT INTO smartroute_query (id,mainquery,use_wizard) VALUES ('$route[0]','1','1')");
132         }   
133         
134     if(!empty($route[0]) && !DB::IsError($route)) return $route[0];
135     
136     return null;
137     }
138
139     
140 // delete a smartroute entry   
141 function smartroutes_del($id) {
142     global $db;
143
144     sql("DELETE FROM smartroute WHERE id='$id'");
145     sql("DELETE FROM smartroute_query WHERE id='$id'");   
146     sql("DELETE FROM smartroute_dest WHERE id='$id'");
147
148     return null;
149 }
150
151
152 // save a smartroute entry
153 function smartroutes_save($id) {
154     global $db;   
155
156     $id = $db->escapeSimple(isset($_POST['id']) ? $_POST['id'] : '');
157     if(empty($id)) return false
158
159     // set default destination
160     $destination = $_POST[$_POST["goto".$_POST[smartroute_default_destination]].$_POST[smartroute_default_destination]];
161     $sql = " `destination` = '".$db->escapeSimple($destination)."',";
162     
163     // set fax destination   
164     if(isset($_POST["gotoFAX"])) {
165         $faxdestination = $_POST[$_POST["gotoFAX"]."FAX"];
166         $sql .= " `faxdestination` = '".$db->escapeSimple($faxdestination)."',";
167         }
168     
169     foreach ($_POST as $key => $value) {
170         if($key == 'trunkdefault' && $value == '1') {
171             // only one smartroute can be the trunk default so disable all
172             sql("UPDATE `smartroute` SET `trunkdefault` = '0'");           
173             }
174         
175         switch ($key) {
176             case 'faxenabled':
177                 // FIX true/false
178                 if($value == 'true') {
179                     $value = '1';
180                 }
181                 else {
182                     $value = '0';
183                 }
184                 
185             case 'name':
186             case 'search-type':
187             case 'limitciddigits':
188             case 'limitdiddigits':
189             case 'dbengine':
190             case 'odbc-dsn':
191             case 'mysql-host':
192             case 'mysql-database':
193             case 'mysql-username':
194             case 'mysql-password':           
195             case 'privacyman':
196             case 'pmmaxretries':
197             case 'pmminlength':
198             case 'alertinfo':
199             case 'ringing':
200             case 'mohclass':
201             case 'description':
202             case 'grppre':
203             case 'delay_answer':
204             case 'pricid':
205             case 'destination':
206             case 'faxdetection':
207             case 'legacy_email':
208             case 'trunkdefault':
209             case 'faxdetectionwait':
210                 $sql_value = $db->escapeSimple($value);
211                 $sql .= " `$key` = '$sql_value',";
212                 break;
213             default:
214             }
215         }
216         
217     if ($sql == '') {
218         return false;
219         }
220         
221     $sql = substr($sql,0,(strlen($sql)-1)); //strip off tailing ','
222     $sql_update = "UPDATE `smartroute` SET"."$sql WHERE `id` = '$id'";
223     
224     // UPDATE THE ACTUAL SMARTROUTE
225     sql($sql_update);
226     
227     // ****** now update the main query - DON'T DELETE BECAUSE WE MAY BE *LIVE* SO ONLY UPDATE MAIN QUERY
228     $sql = "UPDATE `smartroute_query` SET ";
229     
230     if(isset($_POST['smartroute_mainquery_wizard'])) {
231         if($_POST['smartroute_mainquery_wizard'] == 'yes') {
232             $sql .= " use_wizard='1', ";
233             }
234         else {
235             $sql .= " use_wizard='0', ";
236             }
237         }
238     
239     if(isset($_POST['smartroute_query_wiz_table'])) {
240         $wiz_table = $db->escapeSimple(isset($_POST['smartroute_query_wiz_table'])?$_POST['smartroute_query_wiz_table'] :'');
241         $wiz_findcolumn = $db->escapeSimple(isset($_POST['smartroute_query_wiz_scol'])?$_POST['smartroute_query_wiz_scol'] :'');
242         $wiz_matchvar = $db->escapeSimple(isset($_POST['smartroute_query_wiz_mvar'])?$_POST['smartroute_query_wiz_mvar'] :'');
243         $wiz_retcolumn = $db->escapeSimple(isset($_POST['smartroute_query_wiz_rcol'])?$_POST['smartroute_query_wiz_rcol'] :'');
244         
245         $sql .= "
246             wiz_table='$wiz_table',
247             wiz_findcolumn='$wiz_findcolumn',
248             wiz_retcolumn='$wiz_retcolumn',
249             wiz_matchvar='$wiz_matchvar',
250             ";
251         }
252         
253     if(isset($_POST['smartroute_query_adv_sql'])) {
254         $adv_query = $db->escapeSimple(isset($_POST['smartroute_query_adv_sql'][0])?$_POST['smartroute_query_adv_sql'][0] : '');
255         $adv_varname1 = $db->escapeSimple(isset($_POST['smartroute_query_adv_var1'][0])?$_POST['smartroute_query_adv_var1'][0]: '');
256         $adv_varname2 = $db->escapeSimple(isset($_POST['smartroute_query_adv_var2'][0])?$_POST['smartroute_query_adv_var2'][0]: '');
257         $adv_varname3 = $db->escapeSimple(isset($_POST['smartroute_query_adv_var3'][0])?$_POST['smartroute_query_adv_var3'][0]: '');
258         $adv_varname4 = $db->escapeSimple(isset($_POST['smartroute_query_adv_var4'][0])?$_POST['smartroute_query_adv_var4'][0]: '');
259         $adv_varname5 = $db->escapeSimple(isset($_POST['smartroute_query_adv_var5'][0])?$_POST['smartroute_query_adv_var5'][0]: '');
260         
261         $sql .= "
262             adv_query='$adv_query',
263             adv_varname1='$adv_varname1',
264             adv_varname2='$adv_varname2',
265             adv_varname3='$adv_varname3',
266             adv_varname4='$adv_varname4',
267             adv_varname5='$adv_varname5',
268             ";
269         }   
270     $sql = trim($sql); // strip trailing whitespace so we can drop the last ','   
271     $sql = substr($sql,0,(strlen($sql)-1)); //strip off tailing ','       
272     $sql .= " WHERE (id='$id' AND mainquery='1')";
273         
274     // UPDATE THE ACTUAL SMARTROUTE MAIN QUERY
275     sql($sql);
276     
277     // DELETE THE SECONDARY QUERIES USED TO PULL VARS/VALUES (less critical)
278     sql("DELETE FROM smartroute_query WHERE id='$id' AND mainquery='0'");
279
280     // RE-ADD THE SECONDARY QUERIES
281     if(isset($_POST['smartroute_query_adv_sql'])) {
282         $total = count($_POST['smartroute_query_adv_sql']);
283                 
284         foreach($_POST['smartroute_query_adv_sql'] as $curr_row => $query_adv_sql_row) {
285             if($curr_row == 0) {
286                 continue; // skip main query already handled
287                 }
288             $adv_query = $db->escapeSimple(isset($_POST['smartroute_query_adv_sql'][$curr_row])?$_POST['smartroute_query_adv_sql'][$curr_row] : '');
289             $adv_varname1 = $db->escapeSimple(isset($_POST['smartroute_query_adv_var1'][$curr_row])?$_POST['smartroute_query_adv_var1'][$curr_row]: '');
290             $adv_varname2 = $db->escapeSimple(isset($_POST['smartroute_query_adv_var2'][$curr_row])?$_POST['smartroute_query_adv_var2'][$curr_row]: '');
291             $adv_varname3 = $db->escapeSimple(isset($_POST['smartroute_query_adv_var3'][$curr_row])?$_POST['smartroute_query_adv_var3'][$curr_row]: '');
292             $adv_varname4 = $db->escapeSimple(isset($_POST['smartroute_query_adv_var4'][$curr_row])?$_POST['smartroute_query_adv_var4'][$curr_row]: '');
293             $adv_varname5 = $db->escapeSimple(isset($_POST['smartroute_query_adv_var5'][$curr_row])?$_POST['smartroute_query_adv_var5'][$curr_row]: '');
294             
295             $sql = "INSERT INTO smartroute_query
296             (id,mainquery,use_wizard,adv_query,adv_varname1,adv_varname2,adv_varname3,adv_varname4,adv_varname5)
297             VALUES
298             ('$id','0','0','$adv_query','$adv_varname1','$adv_varname2','$adv_varname3','$adv_varname4','$adv_varname5')";
299         
300             // UPDATE THE ACTUAL SMARTROUTE QUERY
301             sql($sql);
302             }
303         }   
304         
305     // SAVE THE DESTINATIONS
306     if(isset($_POST['smartroute_dest_match'])) {
307         $total = count($_POST['smartroute_dest_match']);
308         $used_matchkeys = array();
309         
310         foreach($_POST['smartroute_dest_match'] as $curr_row => $dest_match_row) {
311             $matchkey = $db->escapeSimple(isset($_POST['smartroute_dest_match'][$curr_row])?$_POST['smartroute_dest_match'][$curr_row] : '');
312             $extvar = $db->escapeSimple(isset($_POST['smartroute_dest_extvar'][$curr_row])?$_POST['smartroute_dest_extvar'][$curr_row] : '');
313             $destination_set = $db->escapeSimple(isset($_POST['smartroute_dest'][$curr_row])?$_POST['smartroute_dest'][$curr_row] : '');
314             $failover_set = $db->escapeSimple(isset($_POST['smartroute_faildest'][$curr_row])?$_POST['smartroute_faildest'][$curr_row] : '');
315             
316             $destination = $_POST[$_POST["goto".$destination_set].$destination_set];
317             $failover = $_POST[$_POST["goto".$failover_set].$failover_set];
318             
319             $matchkey = trim($matchkey);
320             if(empty($matchkey)) {
321                 // skip empty values
322                 continue;
323                 }
324         
325             // UPDATE THE ACTUAL SMARTROUTE DESTINATION
326             $row = $db->getRow("SELECT * FROM smartroute_dest WHERE id='$id' and matchkey='$matchkey'");
327             
328             if(count($row) == 0) {
329                 sql("INSERT INTO smartroute_dest (id, matchkey, extvar, destination, failover_dest) VALUES ('$id', '$matchkey', '$extvar', '$destination', '$failover')");
330                 }
331             else {
332                 sql("UPDATE smartroute_dest SET extvar='$extvar', destination='$destination', failover_dest='$failover' WHERE id='$id' and matchkey='$matchkey'");
333                 }
334             
335             $used_matchkeys[] = $matchkey;
336             }
337             
338         // delete any destinations no longer in use
339         $sql = "SELECT * FROM smartroute_dest WHERE id='$id'";
340         $rows = sql($sql,"getAll",DB_FETCHMODE_ASSOC);
341         
342         foreach($rows as $row) {
343             if(!in_array($row['matchkey'], $used_matchkeys)) {
344                 $delkey = $row['matchkey'];
345                 sql("DELETE FROM smartroute_dest WHERE (id='$id' AND matchkey='$delkey')");
346                 }           
347             }           
348         }
349     else {
350         // no destination matches left
351         sql("DELETE FROM smartroute_dest WHERE id='$id'");
352         }
353     }
354
355     
356 // return an array of smartroute destinations   
357 function smartroutes_destinations() {
358     $destinations = array();
359     $smartroutes = smartroutes_list();
360
361     // first destination is static inbound routes
362     //
363     // ** in case we translated a DID and want to process based on static inbound routes)
364     // ** or if a smartroute is set for default trunk call processing but we want to hand off to static inbound routes
365     $destinations[] = array('destination' => 'from-pstn,${EXTEN},1','description' => '* FreePBX Std Inbound Routes *');
366     
367     if(isset($smartroutes)) {
368         foreach($smartroutes as $route){
369             $destinations[] = array('destination' => 'smartroute-'.$route['id'].',${EXTEN},1','description' => $route['name']);
370             }
371         }
372         
373     if(count($destinations)) return $destinations;   
374     return null;   
375 }
376
377
378 // for debugging get_config
379 function ob_file_callback($buffer)
380 {
381   global $ob_file;
382   fwrite($ob_file,$buffer);
383 }
384
385
386 // write the dialplan for smartroutes
387 function smartroutes_get_config($engine) {
388     global $ext;
389     global $version;
390     $replacements = array ('\\' => '\\\\','"' => '\\"','\'' => '\\\'',' ' => '\\ ',',' => '\\,','(' => '\\(',')' => '\\)','.' => '\\.','|' => '\\|' );   
391     
392     if($engine != 'asterisk') {
393         return;       
394         }
395
396     $app_set_16 = false;
397     
398     if(version_compare($version, "1.4", "gt")) {
399         $asterisk_config = smartroutes_read_config('/etc/asterisk/asterisk.conf');   
400         if(isset($asterisk_config['compat']['app_set'])) {
401             if($asterisk_config['compat']['app_set'] == "1.6") {
402                 // this changes the way we "set" variables
403                 // with this setting, we cannot quote variable values
404                 // without this setting we must quote variable values that have a comma (like ODBC multi-value return)
405                 $app_set_16 = true;
406             
407                 // we only use this for the database multi-value return
408                 }
409             }
410         }
411         
412     // for debugging get_config
413 //    global $ob_file;
414 //    $ob_file = fopen('/tmp/smartroute.log','w');
415 //    ob_start('ob_file_callback');
416
417        // do we need to configure smartroutes to handle all trunk calls (before static inbound routes)       
418        $trunk_default_gotoroute = smartroutes_get_trunkdefault_gotoroute();
419        if($trunk_default_gotoroute != null) {
420            // smartroutes are taking point on inbound trunk calls
421            // create a new from-trunk context
422            $ext->add('from-trunk', '_.', '', new ext_setvar('__FROM_DID','${EXTEN}'));
423            $ext->add('from-trunk', '_.', '', new ext_setvar('__CATCHALL_DID','${EXTEN}'));
424            $ext->add('from-trunk', '_.', '', new ext_goto($trunk_default_gotoroute[0]));       
425            $ext->add('from-trunk', 'i', '', new ext_setvar('__CATCHALL_DID','${INVALID_EXTEN}'));
426            $ext->add('from-trunk', 'i', '', new ext_goto($trunk_default_gotoroute[0]));
427
428            // to get to the original static "inbound routes" we go to "from-pstn" (which is the sole inclusion of the existing from-trunk)       
429            }     
430         
431
432     // *** FIRST FIX CATCHALL TO SET FROM_DID  (we need this when the catch-all is routed to smartroutes for processing - that way we can search on FROM_DID)
433     // NEED THE INVALID EXTENSION MATCH FOR DID'S WITH + IN FRONT (like Level3)
434     $ext->add('ext-did-catchall', 'i', '', new ext_noop('Catch-All DID Match - Found ${INVALID_EXTEN} - You probably want a DID for this.'));
435        $ext->add('ext-did-catchall', 'i', '', new ext_setvar('__CATCHALL_DID','${INVALID_EXTEN}'));
436     $ext->add('ext-did-catchall', 'i', '', new ext_goto('ext-did,s,1'));       
437         
438     // on FreePBX 2.8 ours is the first line for this context
439        $ext->add('ext-did-catchall', '_.', '', new ext_setvar('__FROM_DID','${EXTEN}'));
440        // actually, FreePBX is setting FROM_DID in ext-did-001 as 's' so let's keep our own var too *** MAKE SURE TO USE _. so we can get non-numeric DID's too
441        $ext->add('ext-did-catchall', '_.', '', new ext_setvar('__CATCHALL_DID','${EXTEN}'));
442     
443     // get list of routes
444     $smartroutes = smartroutes_list();
445     $odbc_queries = array();
446     
447     if(is_array($smartroutes)) {
448         foreach($smartroutes as $smartroute) {
449             $id = $smartroute['id'];
450             $context = "smartroute-".$smartroute['id'];
451             $extension = '_X.';
452             $smartroute_queries = smartroutes_get_queries($id);
453             $smartroute_dests = smartroutes_get_dests($id);
454
455             // PREP QUERIES FIRST
456             if(version_compare($version, "1.6", "lt")) {
457                 $escapeMySQL = true;
458                 }
459             else {
460                 $escapeMySQL = false;
461                 }
462             
463             // find main query AND prepare query AND escape mysql if necessary
464             $main_query = 0;
465             foreach ($smartroute_queries as $index => &$query) {
466                 
467                 if($smartroute_queries[$index]['mainquery'] == 1 && $smartroute_queries[$index]['use_wizard'] == 1) {
468                     if(!empty($query['wiz_table'])) {
469                         // we build the query from the wizard fields
470                         $smartroute_queries[$index]['adv_query'] = "SELECT ".$query['wiz_retcolumn']." FROM ".$query['wiz_table']." WHERE ".$query['wiz_findcolumn']." = '\${".$query['wiz_matchvar']."}'";
471                         }
472                     else {
473                         $smartroute_queries[$index]['adv_query'] = ""; // blank if no query
474                         }
475                     }
476                 
477                 if($smartroute['dbengine'] == 'mysql' && $escapeMySQL) {
478                     // version 1.2/1.4 asterisk requires escaping inline mysql searches
479                     $smartroute_queries[$index]['query'] = str_replace(array_keys($replacements), array_values($replacements), $smartroute_queries[$index]['adv_query']);
480                     }
481                 else {
482                     $smartroute_queries[$index]['query'] = $smartroute_queries[$index]['adv_query'];
483                     }
484
485                 // record index of main query
486                 if($query['mainquery'] == '1') {
487                     $main_query = $index;
488                     }
489
490                 // save query index (how we build unique query names)
491                 $smartroute_queries[$index]['index'] = $index;                   
492                 // BUILD ODBC QUERIES (and assign query names)
493                 // /etc/asterisk/func_odbc.conf
494                 if($smartroute['dbengine'] == 'odbc') {
495                     // create odbc query
496                     $odbc_query = smartroutes_create_odbc_query($smartroute, $smartroute_queries[$index]);
497                     // note odbc query name for this query
498                     $smartroute_queries[$index]['odbc_query'] = $odbc_query;
499
500                     // store to write to file when done
501                     $odbc_queries[] = $odbc_query;
502                     }
503                     
504                 if($smartroute_queries[$index]['use_wizard'] == 1) {
505                     $smartroute_queries[$index]['return_count'] = 1;
506                     $smartroute_queries[$index]['adv_varname1'] = "DBRESULT";
507                     }
508                 else {
509                     // count the numbers of returns needed (by counting commas before FROM
510                     // ------------------------------------------------------------------------------------------------------------
511                     // NOTE!!!: not precise because complex queries with sub-function returns will increase value (but we cap at 5)
512                     // ------------------------------------------------------------------------------------------------------------
513                     $sqlparts = explode(" FROM ", strtoupper($smartroute_queries[$index]['query']));
514                     $smartroute_queries[$index]['return_count'] = substr_count($sqlparts[0], ",")+1;           
515                     // we cap at 5 returns tracked
516                     if($smartroute_queries[$index]['return_count'] > 5)
517                         $smartroute_queries[$index]['return_count'] = 5;
518                         
519                     // make sure we have varnames for the necessary returns AND BUILD ARRAY var
520                     $smartroute_queries[$index]['array_var'] = "ARRAY(";
521                     $smartroute_queries[$index]['mysql_array_var'] = "";
522                     
523                     for($currVar = 1; $currVar <= $smartroute_queries[$index]['return_count']; $currVar++) {
524                         if(empty($smartroute_queries[$index]['adv_varname'.$currVar])) {
525                             $smartroute_queries[$index]['adv_varname'.$currVar] = "DBQUERY_RET_".$index."_".$currVar;
526                             }
527                             
528                         // assemble array var for assigning result of db query
529                         if($currVar > 1) {
530                             $smartroute_queries[$index]['array_var'] .= ",";
531                             $smartroute_queries[$index]['mysql_array_var'] .= " ";
532                             }                       
533                         $smartroute_queries[$index]['array_var'] .= $smartroute_queries[$index]['adv_varname'.$currVar];
534                         $smartroute_queries[$index]['mysql_array_var'] .= $smartroute_queries[$index]['adv_varname'.$currVar];
535                         }
536                     $smartroute_queries[$index]['array_var'] .= ")";
537
538                     if($smartroute['dbengine'] == 'mysql' && $escapeMySQL) {
539                         // version 1.2/1.4 asterisk requires escaping inline mysql searches
540                         $smartroute_queries[$index]['mysql_array_var'] = str_replace(array_keys($replacements), array_values($replacements), $smartroute_queries[$index]['mysql_array_var']);
541                         }                   
542                     }                                       
543                 }
544
545             // **** START WRITING DIALPLAN FOR THIS ROUTE
546             // **** first start with the invalid handler (when the sip provider sends non-numeric characters in DID (like '+')
547             $ext->add($context, 'i', '', new ext_noop('Smartroute invalid DID handler - FIX EXTENSION VAR'));
548             // fix EXTEN and send back through the smartroute
549             if($smartroute['limitdiddigits'] != "" && $smartroute['limitdiddigits'] > '0') {
550                 // only use the last xx digits from did (effectively stripping unnecessary prefixes)
551                 $ext->add($context, 'i', '', new ext_setvar('FIX_EXTEN','${INVALID_EXTEN:-'.$smartroute['limitdiddigits'].'}'));
552                 }       
553             else {
554                 $ext->add($context, 'i', '', new ext_setvar('FIX_EXTEN','${INVALID_EXTEN}'));
555                 }
556
557             $ext->add($context, 'i', '', new ext_setvar('TMP','0'));
558             $ext->add($context, 'i', '', new ext_setvar('ANS',''));
559             $ext->add($context, 'i', '', new ext_setvar('END','${LEN(${FIX_EXTEN})}'));
560             $ext->add($context, 'i', 'clean_ext_loop_top', new ext_gotoif('$["${TMP}" = "${END}"]','clean_ext_done'));
561             $ext->add($context, 'i', '', new ext_setvar('TCH','${FIX_EXTEN:${TMP}:1}'));
562             $ext->add($context, 'i', '', new ext_gotoif('$["${TCH}" < "0"]','clean_ext_ignore_char'));
563             $ext->add($context, 'i', '', new ext_gotoif('$["${TCH}" > "9"]','clean_ext_ignore_char'));
564             $ext->add($context, 'i', '', new ext_setvar('ANS','${ANS}${TCH}'));
565             $ext->add($context, 'i', 'clean_ext_ignore_char', new ext_setvar('TMP','$[${TMP}+1]'));
566             $ext->add($context, 'i', '', new ext_goto('clean_ext_loop_top'));           
567             $ext->add($context, 'i', 'clean_ext_done', new ext_setvar('FIX_EXTEN','${ANS}'));       
568             $ext->add($context, 'i', '', new ext_goto('smartroute-'.$id.',${FIX_EXTEN},1'));       
569
570             // *** now include handler in case passed extension is 's' - pull EXTEN from CATCHALL_DID
571             $ext->add($context, 's', '', new ext_noop('Smartroute passed generic extension s handler - FIX EXTENSION VAR'));
572             $ext->add($context, 's', '', new ext_gotoif('$["${CATCHALL_DID}" != "s" & "${CATCHALL_DID}empty" != "empty"]','smartroute-'.$id.',${CATCHALL_DID},1'));
573             $ext->add($context, 's', '', new ext_gotoif('$["${FROM_DID}" != "s" & "${FROM_DID}empty" != "empty"]','smartroute-'.$id.',${FROM_DID},1'));
574             $ext->add($context, 's', '', new ext_goto('smartroute-'.$id.',${CALLERID(dnid)},1'));           
575             
576             // proceed with standard dialplan
577             $ext->add($context, $extension, '', new ext_noop('Smartroute: Start Standard Processing - DB Routing'));
578             
579             // if FROM_DID isn't set (OR IS SET AS 's') then set it as the EXTEN passed into this context, or the CATCHALL_DID, or the CALLERID(dnid)
580             $ext->add($context, $extension, '', new ext_execif('$[ "${FROM_DID}" = "" | "${FROM_DID}" = "s"] ','Set','__FROM_DID=${EXTEN}'));
581             $ext->add($context, $extension, '', new ext_execif('$[ "${FROM_DID}" = "" | "${FROM_DID}" = "s"] ','Set','__FROM_DID=${CATCHALL_DID}'));
582             $ext->add($context, $extension, '', new ext_execif('$[ "${FROM_DID}" = "" | "${FROM_DID}" = "s"] ','Set','__FROM_DID=${CALLERID(dnid)}'));
583             
584             if($smartroute['limitdiddigits'] != "" && $smartroute['limitdiddigits'] > '0') {
585                 // only use the last xx digits from did (effectively stripping unnecessary prefixes)
586                 $ext->add($context, $extension, '', new ext_execif('$[ "${FROM_DID}" = "" ] ','Set','__FROM_DID=${EXTEN}'));
587                 $ext->add($context, $extension, '', new ext_setvar('__FROM_DID','${FROM_DID:-'.$smartroute['limitdiddigits'].'}'));
588                 }       
589             else {
590                 // no from_did set so use EXTEN as from_did
591                 $ext->add($context, $extension, '', new ext_execif('$[ "${FROM_DID}" = "" ] ','Set','__FROM_DID=${EXTEN}'));
592                 }           
593
594             // *** write standard inbound route stuff
595             // always set callerID name
596             $ext->add($context, $extension, '', new ext_execif('$[ "${CALLERID(name)}" = "" ] ','Set','CALLERID(name)=${CALLERID(num)}'));
597             
598             // after setting callerID name, strip unnecessary callerID prefix (for routing and accounting purposes)
599             if($smartroute['limitciddigits'] != "" && $smartroute['limitciddigits'] > '0') {
600                 // only use the last xx digits from caller-id (effectively stripping unnecessary prefixes)
601                 $ext->add($context, $extension, '', new ext_setvar('CALLERID(num)','${CALLERID(num):-'.$smartroute['limitciddigits'].'}'));
602                 }                   
603             
604             if (!empty($item['mohclass']) && trim($smartroute['mohclass']) != 'default') {
605                 $ext->add($context, $extension, '', new ext_setmusiconhold($smartroute['mohclass']));
606                 $ext->add($context, $extension, '', new ext_setvar('__MOHCLASS',$smartroute['mohclass']));
607                 }
608
609             // If we require RINGING, signal it as soon as we enter.
610             if ($smartroute['ringing'] === "CHECKED") {
611                 $ext->add($context, $extension, '', new ext_ringing(''));
612                 }
613             if ($smartroute['delay_answer']) {
614                 $ext->add($context, $extension, '', new ext_wait($smartroute['delay_answer']));
615                 }
616                 
617             if ($item['privacyman'] == "1") {
618                 $ext->add($context, $extension, '', new ext_macro('privacy-mgr',$smartroute['pmmaxretries'].','.$smartroute['pmminlength']));
619                 }
620             else {
621                 // if privacymanager is used, this is not necessary as it will not let blocked/anonymous calls through
622                 // otherwise, we need to save the caller presence to set it properly if we forward the call back out the pbx
623                 // note - the indirect table could go away as of 1.4.20 where it is fixed so that SetCallerPres can take
624                 // the raw format.
625                 //
626                 if(version_compare($version, "1.6", "lt")) {
627                     $ext->add($context, $extension, '', new ext_setvar('__CALLINGPRES_SV','${CALLINGPRES_${CALLINGPRES}}'));
628                     }
629                 else {
630                     $ext->add($context, $extension, '', new ext_setvar('__CALLINGPRES_SV','${CALLERPRES()}'));
631                     }
632                 $ext->add($context, $extension, '', new ext_setcallerpres('allowed_not_screened'));
633                 }
634             
635             if (!empty($smartroute['alertinfo'])) {
636                 $ext->add($context, $extension, '', new ext_setvar("__ALERT_INFO", str_replace(';', '\;', $smartroute['alertinfo'])));
637                 }               
638                 
639             if (!empty($smartroute['grppre'])) {
640                 $ext->add($context, $extension, '', new ext_setvar('_RGPREFIX', $smartroute['grppre']));
641                 $ext->add($context, $extension, '', new ext_setvar('CALLERID(name)','${RGPREFIX}${CALLERID(name)}'));
642                 }
643                 
644             // *** FAX DIALPLAN COMPONENTS
645             if (function_exists('fax_get_config') && $smartroute['faxenabled'] == 1) {
646                 $ext->add($context, 'fax', '', new ext_goto('${CUT(FAX_DEST,^,1)},${CUT(FAX_DEST,^,2)},${CUT(FAX_DEST,^,3)}'));               
647                 
648                 $fax=fax_detect($version);
649                 if ($fax['module']) {
650                     $fax_settings['force_detection'] = 'yes';
651                     }
652                 else {
653                     $fax_settings=fax_get_settings();
654                     }
655                 if($fax_settings['force_detection'] == 'yes'){ //dont continue unless we have a fax module in asterisk
656                     if ($smartroute['faxdetection'] == 'nvfax' && !$fax['nvfax']) {
657                         //TODO: add notificatoin to notification panel that this was skipped because NVFaxdetec not present
658                         // skip this one if there is no NVFaxdetect installed on this system
659                           }
660                       else {
661                         // proceed with forced fax detection
662                         if(!isset($smartroute['legacy_email'])) $smartroute['legacy_email'] = null;
663                         if ($smartroute['legacy_email'] === null) {
664                             $ext->splice($context, $extension, 'dest-ext', new ext_setvar('FAX_DEST',str_replace(',','^',$smartroute['faxdestination'])));
665                             }
666                         else {
667                             $ext->splice($context, $extension, 'dest-ext', new ext_setvar('FAX_DEST','ext-fax^s^1'));
668                             if (!empty($smartroute['legacy_email'])) {
669                                 $fax_rx_email = $smartroute['legacy_email'];
670                                 }
671                             else {
672                                 if (!isset($default_fax_rx_email)) {
673                                     $default_address = sql('SELECT value FROM fax_details WHERE `key` = \'fax_rx_email\'','getRow');
674                                     $default_fax_rx_email = $default_address[0];
675                                     }
676                                 $fax_rx_email = $default_fax_rx_email;
677                                 }
678                             $ext->splice($context, $extension, 'dest-ext', new ext_setvar('FAX_RX_EMAIL',$fax_rx_email));
679                             }
680                         $ext->splice($context, $extension, 'dest-ext', new ext_answer(''));
681                         if ($smartroute['faxdetection'] == 'nvfax') {
682                             $ext->splice($context, $extension, 'dest-ext', new ext_playtones('ring'));
683                             $ext->splice($context, $extension, 'dest-ext', new ext_nvfaxdetect($smartrouteroute['faxdetectionwait'].",t"));
684                             }
685                         else {
686                             $ext->splice($context, $extension, 'dest-ext', new ext_wait($smartroute['faxdetectionwait']));
687                             }                         
688                           }
689                     }               
690                 }
691             else {
692                 // blank the fax destination if fax disabled on this route
693                 $ext->splice($context, $extension, 'dest-ext', new ext_setvar('FAX_DEST',''));
694                 }
695                 
696                 
697             // **** DONE WITH STANDARD INBOUND ROUTE DIALPLAN
698             // write the main query in dialplan   
699             $ext->add($context, $extension, '', new ext_noop('Smartroute: '.$smartroute['name']));
700             
701             $mainQueryPresent = false;
702                 
703             if($smartroute['dbengine'] == 'mysql' && !empty($smartroute_queries[$main_query]['query'])) {
704                 $mainQueryPresent = true;
705                 
706                 // write mysql version of query
707                 $ext->add($context, $extension, '', new ext_mysql_connect('connid', $smartroute['mysql-host'],  $smartroute['mysql-username'],  $smartroute['mysql-password'],  $smartroute['mysql-database']));
708                 $ext->add($context, $extension, '', new ext_mysql_query('resultid', 'connid', $smartroute_queries[$main_query]['query']));
709                 if($smartroute_queries[$main_query]['return_count'] == 1) {
710                     // assign result of query to single var
711                     
712                     $ext->add($context, $extension, '', new ext_mysql_fetch('fetchid', 'resultid', $smartroute_queries[$main_query]['adv_varname1']));
713                     }
714                 else {
715                     // assign result of query to array var (multiple results)
716                     $ext->add($context, $extension, '', new ext_mysql_fetch('fetchid', 'resultid', $smartroute_queries[$main_query]['mysql_array_var']));
717                     }
718                 $ext->add($context, $extension, '', new ext_mysql_clear('resultid'));                           
719                 $ext->add($context, $extension, '', new ext_mysql_disconnect('connid'));
720                 $ext->add($context, $extension, '', new ext_execif('$[${fetchid} = 0]', 'Set', $smartroute_queries[$main_query]['adv_varname1'].'='));
721                 $ext->add($context, $extension, '', new ext_gotoif('$[${fetchid} = 0]','no_match_found'));
722                 }
723             else if(!empty($smartroute_queries[$main_query]['odbc_query'])) {
724                 $mainQueryPresent = true;
725                 
726                 // write odbc version of query
727                 if($smartroute_queries[$main_query]['return_count'] == 1) {
728                     if(!$app_set_16) {
729                         // need to put quote around value "just in case" there's a comma
730                         
731                         // assign result of query to single var
732                         $ext->add($context, $extension, '', new ext_setvar($smartroute_queries[$main_query]['adv_varname1'], '"'.$smartroute_queries[$main_query]['odbc_query']['odbc_command'].'"'));
733                         }
734                     else {
735                         // no quotes around value
736                         
737                         // assign result of query to single var
738                         $ext->add($context, $extension, '', new ext_setvar($smartroute_queries[$main_query]['adv_varname1'], $smartroute_queries[$main_query]['odbc_query']['odbc_command']));
739                         }
740                     }
741                 else {
742                     if(!$app_set_16) {
743                         // need to put quote around value "just in case" there's a comma
744                         
745                         // assign result of query to array var (multiple results)
746                         $ext->add($context, $extension, '', new ext_setvar($smartroute_queries[$main_query]['array_var'], '"'.$smartroute_queries[$main_query]['odbc_query']['odbc_command'].'"'));
747                         }
748                     else {
749                         // no quotes
750                         
751                         // assign result of query to array var (multiple results)
752                         $ext->add($context, $extension, '', new ext_setvar($smartroute_queries[$main_query]['array_var'], $smartroute_queries[$main_query]['odbc_query']['odbc_command']));
753                         }
754                     }
755                 }
756                 
757             if($mainQueryPresent) {               
758                 // write destination gotos
759                 if(!empty($smartroute_dests)) {
760                     // set match-type
761                     switch($smartroute['search-type']) {
762                         case 'LESSER':
763                             $matchtype = "<";
764                             break;
765                             
766                         case 'GREATER':
767                             $matchtype = ">";
768                             break;
769                             
770                         case 'EXACT':
771                         default:
772                             $matchtype = "=";
773                             break;
774                         }
775                         
776                     // if main query first var was setting a global var, strip the __ prefix
777                     $smartroute_queries[$main_query]['adv_varname1'] = trim($smartroute_queries[$main_query]['adv_varname1']);
778                     if($smartroute_queries[$main_query]['adv_varname1'][0] == '_' && $smartroute_queries[$main_query]['adv_varname1'][0] == '_') {
779                         $smartroute_queries[$main_query]['adv_varname1'] = substr($smartroute_queries[$main_query]['adv_varname1'], 2);
780                         }                       
781                 
782                     // first write the goto statements (redirect to another part of this section - multiline)                           
783                     foreach($smartroute_dests as $index => $dest) {
784                         $smartroute_dests[$index]['index'] = $index;
785                             
786                         $ext->add($context, $extension, '', new ext_gotoif('$["${'.$smartroute_queries[$main_query]['adv_varname1'].'}" '.$matchtype.' "'.$dest['matchkey'].'"]',"destination".$index));
787                         }
788                     }           
789                 }
790                 
791             // write the default destination goto
792             $ext->add($context, $extension, 'no_match_found', new ext_noop('No Smartroute Match: Goto Default Destination'));
793             
794             if(!empty($smartroute['destination'])) {
795                 $ext->add($context, $extension, '', new ext_goto($smartroute['destination']));
796                 }
797             $ext->add($context, $extension, '', new ext_hangup(''));
798             
799             // write the destination sections
800             if(!empty($smartroute_dests)) {
801                 // first write the goto statements (redirect to another part of this section - multiline)
802                 foreach($smartroute_dests as $dest) {
803                     $macrodestination = false;                   
804                     
805                     // change destination trunks from ext-trunk to dialout-trunk
806                     // NOTE: will require dialout rules to be set appropriately (ex: allow international if attempting international) or call will fail
807                     if(strstr($dest['destination'], "ext-trunk")) {
808                         // fix trunk destinations to use override extension and the dialout macro
809                         $destparts = explode(",", $dest['destination']);
810                         $dest['destination'] = "Macro(dialout-trunk,".$destparts[1].",".(empty($dest['extvar'])?"${FROM_DID}":$dest['extvar']).",)";
811                         $macrodestination = true;
812                         }
813                         
814                     // also exchange EXTEN for SR_OR_EXTVAR if set FOR ANY PRIMARY DESTINATION
815                     // also set the actual context for any destination with an ,s, extension FOR ANY PRIMARY DESTINATION
816                     $dest['extvar'] = trim($dest['extvar']);
817                     if(!empty($dest['extvar'])) {
818                         $dest['destination'] = str_replace('${EXTEN}',$dest['extvar'],$dest['destination']);
819                         $dest_str_part_pos = strpos($dest['destination'], ',s,');
820                         if(is_numeric($dest_str_part_pos) ) {
821                             $dest['destination'] = $dest['extvar'].substr($dest['destination'],$dest_str_part_pos);
822                             }
823                         }                       
824                     
825                     // set processing vars
826                     $ext->add($context, $extension, 'destination'.$dest['index'], new ext_setvar('SR_PRIMARY_DEST', str_replace(',','^',$dest['destination'])));
827                     $ext->add($context, $extension, '', new ext_setvar('SR_FAILOVER_DEST', str_replace(',','^',$dest['failover_dest'])));
828                     $ext->add($context, $extension, '', new ext_setvar('SR_OR_EXTVAR', $dest['extvar']));
829                     if($macrodestination) {
830                         // this tells processing section to use a macro and not goto for primary destination (allows failover)
831                         $ext->add($context, $extension, '', new ext_setvar('SR_MACRO', "YES"));
832                         $ext->add($context, $extension, '', new ext_setvar('SR_MACRO_TRUNK', $destparts[1]));
833                         }                   
834                     else {
835                         // clear these
836                         $ext->add($context, $extension, '', new ext_setvar('SR_MACRO', ""));
837                         $ext->add($context, $extension, '', new ext_setvar('SR_MACRO_TRUNK', ""));                       
838                         }
839                     $ext->add($context, $extension, '', new ext_goto("process_match_found"));
840                     }
841                 }           
842
843             // CLEANUP *** SHOULD NOT REACH THIS LINE OF DIALPLAN CODE ***
844             $ext->add($context, $extension, '', new ext_noop('Smartroute Error - should never get here.'));
845             $ext->add($context, $extension, '', new ext_hangup(''));
846                             
847             // write the section to process found match"
848             // first write the secondary queries to pull data
849             // then see if we need to use the extvar
850             // finally attempt transfer to primary and if fails then attempt transfer to failover_dest
851             $ext->add($context, $extension, 'process_match_found', new ext_noop('Process Match Found'));
852             
853             if(count($smartroute_queries) > 1) {
854
855                 // connect 1 time, run queries and then close connection afterward
856                 if($smartroute['dbengine'] == 'mysql') {
857                     $ext->add($context, $extension, '', new ext_mysql_connect('connid', $smartroute['mysql-host'],  $smartroute['mysql-username'],  $smartroute['mysql-password'],  $smartroute['mysql-database']));
858                     }           
859                 
860                 // start by writing secondary queries
861                 foreach ($smartroute_queries as $index => $query) {
862                     if($smartroute_queries[$index]['mainquery'] == 1) {
863                         // skip main query (processed above)
864                         continue;
865                         }               
866                     
867                     if($smartroute['dbengine'] == 'mysql') {
868                         // write mysql version of query
869                         $ext->add($context, $extension, '', new ext_mysql_query('resultid', 'connid', $query['query']));
870                         if($query['return_count'] == 1) {                           
871                             // assign result of query to single var
872                             $ext->add($context, $extension, '', new ext_mysql_fetch('fetchid', 'resultid', $query['adv_varname1']));
873                             }
874                         else {
875                             // assign result of query to array var (multiple results)
876                             $ext->add($context, $extension, '', new ext_mysql_fetch('fetchid', 'resultid', $query['mysql_array_var']));                   
877                             }
878                         $ext->add($context, $extension, '', new ext_mysql_clear('resultid'));                           
879                         }
880                     else {
881                         // write odbc version of query
882                         if($query['return_count'] == 1) {
883                             if(!$app_set_16) {
884                                 // need to put quote around value "just in case" there's a comma
885                         
886                                 // assign result of query to single var
887                                 $ext->add($context, $extension, '', new ext_setvar($query['adv_varname1'], '"'.$query['odbc_query']['odbc_command'].'"'));
888                                 }
889                             else {
890                                 // no quotes
891                                 // assign result of query to single var
892                                 $ext->add($context, $extension, '', new ext_setvar($query['adv_varname1'], $query['odbc_query']['odbc_command']));
893                                 }
894                             }
895                         else {
896                             if(!$app_set_16) {
897                                 // need to put quote around value "just in case" there's a comma
898                                 // assign result of query to array var (multiple results)
899                                 $ext->add($context, $extension, '', new ext_setvar($query['array_var'], '"'.$query['odbc_query']['odbc_command'].'"'));
900                                 }
901                             else {
902                                 // no quotes
903                                 // assign result of query to array var (multiple results)
904                                 $ext->add($context, $extension, '', new ext_setvar($query['array_var'], $query['odbc_query']['odbc_command']));
905                                 }
906                             }
907                         }
908                     }
909     
910                 // connect 1 time, run queries and then close connection afterward
911                 if($smartroute['dbengine'] == 'mysql') {
912                     $ext->add($context, $extension, '', new ext_mysql_disconnect('connid'));
913                     }
914                 }           
915                 
916             // if extvar not blank, set FROM_DID=extvar (ext-trunk will dial whatever is in FROM_DID)
917             // not needed for dialout-trunk: $ext->add($context, $extension, '', new ext_execif('$["${SR_OR_EXTVAR}empty" != "empty"]', new ext_setvar('FROM_DID', '${SR_OR_EXTVAR}'));
918
919             // when doing the actual dialing, we use the dial-out macros dialout-trunk instead of the normal ext-trunk (that doesn't allow failover)
920             // if we used the normal ext-trunk, we could set FROM_DID=SR_OR_EXTVAR in order to translate to a new extension on a destination trunk
921             $ext->add($context, $extension, '', new ext_gotoif('$["${SR_MACRO}empty" != "empty"]',"process_macrotrunk"));
922             
923             // standard goto on primary (won't allow failover
924             $ext->add($context, $extension, '', new ext_goto('${CUT(SR_PRIMARY_DEST,^,1)},${CUT(SR_PRIMARY_DEST,^,2)},${CUT(SR_PRIMARY_DEST,^,3)}'));
925             $ext->add($context, $extension, '', new ext_goto('${CUT(SR_FAILOVER_DEST,^,1)},${CUT(SR_FAILOVER_DEST,^,2)},${CUT(SR_FAILOVER_DEST,^,3)}'));
926             $ext->add($context, $extension, '', new ext_hangup(''));           
927             
928             // primary trunk goto allows macro and failover
929             $ext->add($context, $extension, 'process_macrotrunk', new ext_noop('Process Trunk Destination with Failover'));
930             $ext->add($context, $extension, '', new ext_setvar('INTRACOMPANYROUTE', 'YES')); // necessary so macro-dialout-trunk won't set callerid to trunk default - this one is overkill but what the heck
931             $ext->add($context, $extension, '', new ext_setvar('KEEPCID', 'TRUE')); // necessary so macro-dialout-trunk won't set callerid to trunk default
932             $ext->add($context, $extension, '', new ext_setvar('OUTDISABLE_${SR_MACRO_TRUNK}', 'off')); // necessary so we can override disabled outbound dialing for trunk
933             
934             // if we failover, macro-dialout-trunk resets the callerid so we have to save it here
935             $ext->add($context, $extension, '', new ext_setvar('SAVECID', '${CALLERID(number)}')); // necessary so macro-dialout-trunk won't set callerid to trunk default           
936                 
937             $ext->add($context, $extension, '', new ext_execif('$["${SR_OR_EXTVAR}empty" = "empty"]', 'Set', 'SR_OR_EXTVAR=${FROM_DID}'));
938             $ext->add($context, $extension, '', new ext_macro('dialout-trunk','${SR_MACRO_TRUNK},${SR_OR_EXTVAR},'));
939             // if primary fails then go to failover destination
940             // if we failover, macro-dialout-trunk resets the callerid so we have to RESTORE it here
941             $ext->add($context, $extension, '', new ext_setvar('CALLERID(number)', '${SAVECID}')); // necessary so macro-dialout-trunk won't set callerid to trunk default
942             
943             $ext->add($context, $extension, '', new ext_goto('${CUT(SR_FAILOVER_DEST,^,1)},${CUT(SR_FAILOVER_DEST,^,2)},${CUT(SR_FAILOVER_DEST,^,3)}'));       
944               $ext->add($context, $extension, '', new ext_hangup(''));           
945               
946               // add hangup extension/state
947             $ext->add($context, 'h', '', new ext_macro('hangupcall',''));                 
948             }
949         }
950         
951     // NOW WRITE ODBC QUERY FILE
952     if(count($odbc_queries) > 0) {
953         smartroutes_save_odbc_funcs($odbc_queries);
954         }
955
956     // for debugging get_config
957 //    ob_end_flush();
958 //    fclose($ob_file);       
959     }
960
961
962 // helper function for the dialplan generation code - process an individual odbc query       
963 function smartroutes_create_odbc_query($smartroute, $query) {
964     $odbc_query = array();
965     $clean_name = preg_replace("/[^a-zA-Z0-9\s]/", "_", $smartroute['name']);
966     
967     // build odbc query vals
968     $odbc_query['prefix'] = "SMARTRDB";
969     $odbc_query['dsn'] = $smartroute['odbc-dsn'];
970     $odbc_query['label'] = strtoupper($clean_name.$query['index']);
971     $odbc_query['orig_query'] = $query['query'];
972     $odbc_query['query'] = $query['query'];   
973     $odbc_query['args'] = array();
974     // build the odbc command
975     $odbc_query['odbc_command'] = '${'.$odbc_query['prefix']."_".$odbc_query['label']."(";
976     
977     // get query args and convert query to arg-based
978     preg_match_all("/\{[\s]*([^:}]*)[:\}]/", $odbc_query['query'], $ast_vars);
979
980     // preg_match returns the entire match (with brackets) in array[0] and just the matched parts in array[1]
981     $ast_vars = $ast_vars[1];
982     
983     if(is_array($ast_vars) && count($ast_vars)) {
984         // get just unique values
985         $ast_vars = array_unique($ast_vars);
986         $currArg = 1;
987         // replace with args
988         foreach($ast_vars as $index => $ast_var) {
989             $arg = array();           
990             $arg['arg'] = 'ARG'.$currArg;
991             $arg['var'] = $ast_var;
992             $arg['varnum'] = $currArg;           
993             $odbc_query['args'][] = $arg;
994                         
995             // first replace with arg (with or without ':') AND SQL_ESC
996             // replace where we don't have the ':' (asterisk var substring notation)
997             $odbc_query['query'] = preg_replace("/\{[\s]*".$arg['var']."[\}]/", '{SQL_ESC(\${'.$arg['arg'].'})}', $odbc_query['query']);
998             // replace where we DO have the ':' (asterisk var substring notation)
999             $odbc_query['query'] = preg_replace("/\{[\s]*".$arg['var']."[:]([^\}]*)[\}]/", '{SQL_ESC(\${'.$arg['arg'].':$1})}', $odbc_query['query']);
1000             // continue building odbc command
1001             if($currArg > 1) $odbc_query['odbc_command'] .= ",";
1002             $odbc_query['odbc_command'] .= '${'.$ast_var.'}';
1003             
1004             ++$currArg;           
1005             }       
1006         }   
1007
1008     // finish/close odbc command
1009     $odbc_query['odbc_command'] .= ")}";
1010     return $odbc_query;
1011     }
1012
1013     
1014 // read an asterisk config file into an array   
1015 function smartroutes_read_config($config_file) {
1016     $config = array();
1017     
1018     // open config file for reading
1019     $fh = fopen($config_file, 'r');
1020     
1021     // read current values
1022     $section_name = "";
1023     while($line=fgets($fh))    {
1024         $line = trim($line);
1025         if(empty($line)) continue;
1026         
1027         // found new section
1028         if($line[0] == '[') {
1029             // this is a new odbc function
1030             $section_name = substr($line, 1, -1);
1031             
1032             // just in case there was a comment or something on the end and we didn't strip the end bracket
1033             $end_bracket = strpos($section_name, ']');
1034             if($end_bracket !== false) {
1035                 $section_name = substr($section_name,0,$end_bracket);
1036                 }
1037             $section_name = trim($section_name);
1038             $config[$section_name] = array();
1039             }
1040         else {
1041             $value_setting = explode("=",$line);
1042             }
1043         
1044         if(empty($section_name)) {
1045             $section_name = "general";
1046             }
1047         if(empty($value_setting) || empty($value_setting[0]) || empty($value_setting[1])) continue;
1048         
1049         $value_setting[0] = strtolower(trim($value_setting[0]));
1050
1051         if(isset($value_setting[1][0])) {
1052             if($value_setting[1][0] == '>') {
1053                 // some asterisk assignments use => ...  (like the filepaths in asterisk.conf)   
1054                 $value_setting[1] = substr($value_setting[1],1);
1055                 }
1056             }
1057         $config[$section_name][$value_setting[0]] = trim($value_setting[1]);
1058         }
1059         
1060     // close config file
1061     fclose($fh);
1062     
1063     return $config;   
1064 }   
1065     
1066
1067 // save the Asterisk odbc queries/funcs file /etc/asterisk/func_odbc.conf     
1068 function smartroutes_save_odbc_funcs($odbc_queries) {
1069     global $version;
1070
1071     // note that readsql and writesql are required for asterisk 1.6.x and higher.  In earlier versions, read and write might be required.
1072     // don't forget to SQL_ESC the args in the query sql
1073     $odbc_config = smartroutes_read_config('/etc/asterisk/func_odbc.conf');
1074     
1075     // remove any of our previous smartroute functions
1076     foreach($odbc_config as $funcname => $funcdef) {
1077         $funcname = trim($funcname);
1078         if(strpos($funcname, "SMARTRDB") !== false) {
1079             // remove this previous smartroute func
1080             unset($odbc_config[$funcname]);
1081             }
1082         if(isset($funcdef['prefix'])) {
1083             $funcdef['prefix'] = trim($funcdef['prefix']);
1084             if(strpos($funcdef['prefix'],"SMARTRDB") !== false) {
1085                 // remove this previous smartroute func
1086                 unset($odbc_config[$funcname]);               
1087                 }
1088             }
1089         }   
1090     
1091     // prepare new functions  (overlap our functions on top of existing ones so that we don't remove anything created outside this module)
1092     foreach($odbc_queries as $label => $query) {       
1093         $odbc_config[$query['label']]['prefix'] = $query['prefix'];
1094         $odbc_config[$query['label']]['dsn'] = $query['dsn'];
1095         
1096         if(version_compare($version, "1.6", "lt")) {
1097             $odbc_config[$query['label']]['read'] = $query['query'];
1098             }
1099         else {
1100             $odbc_config[$query['label']]['readsql'] = $query['query'];
1101             }       
1102         } 
1103     
1104     // write the odbc config
1105     $output = array();
1106     foreach($odbc_config as $label => $settings) {
1107         $output[] = "[$label]";
1108         foreach($settings as $key => $value) {
1109             $output[] = "$key = $value";
1110             }
1111         $output[] = "";       
1112         }
1113     // add newline at end
1114     $output[] = "";
1115         
1116     // open config file for writing and truncate to zero length
1117     $fh = fopen('/etc/asterisk/func_odbc.conf', 'w+');       
1118     $output = implode("\n", $output);
1119     fwrite($fh, $output);
1120     
1121     // close config file
1122     fclose($fh);
1123     
1124 }
1125
1126
1127 // when a smartroute is setup for default trunk call processing, provide notification on the static inbound route page
1128 function smartroutes_hook_core($viewing_itemid, $target_menuid) {
1129     $html = '';
1130     
1131     if ($target_menuid == 'did')  {   
1132         $trunk_default_route_name = smartroutes_get_trunkdefault();
1133             
1134         if($trunk_default_route_name != null) {
1135             $html = '<tr><td colspan="2"><h5><hr>';
1136             $html .= '<p><span style="background-color: #CCFFFF; color: black; line-height: 125%; padding:2pt;">&nbsp;<b style="color: red;">'._("Important:").'</b>&nbsp;&nbsp;'._("Inbound trunk calls first processed by SmartRoute:").' ['.$trunk_default_route_name.']&nbsp;</span></p>'."\n";               
1137             $html .= '<hr></h5></td></tr>';
1138             }
1139         }
1140
1141     return $html;
1142     }
1143
1144
1145 // from fax module - provide fax functions for smartroutes (like on the static inbound route pages)   
1146 function smartroutes_fax_hook_core($viewing_itemid, $target_menuid, $smartroute){  // ejr 2-31-11 modified to pass smartroute array
1147   //hmm, not sure why engine_getinfo() isnt being called here?! should probobly read: $info=engine_getinfo();
1148   //this is what serves fax code to inbound routing
1149   $tabindex=null;
1150   $type=isset($_REQUEST['type'])?$_REQUEST['type']:'';
1151   $extension=isset($_REQUEST['extension'])?$_REQUEST['extension']:'';
1152   $cidnum=isset($_REQUEST['cidnum'])?$_REQUEST['cidnum']:'';
1153   $extdisplay=isset($_REQUEST['extdisplay'])?$_REQUEST['extdisplay']:'';
1154
1155   //if were editing, get save parms. Get parms
1156   if ($type != 'setup'){
1157     if(!$extension && !$cidnum){//set $extension,$cidnum if we dont already have them
1158       $opts=explode('/', $extdisplay);$extension=$opts['0'];$cidnum=$opts['1'];
1159     }
1160     
1161     // ejr 2-31-11 modified for smartroute data
1162     //$fax=fax_get_incoming($extension,$cidnum);
1163     if($smartroute['faxenabled'] == '1')
1164         $fax = array('detection'=>$smartroute['faxdetection'], 'detectionwait'=>$smartroute['faxdetectionwait'], 'destination'=>$smartroute['faxdestination'], 'legacy_email'=>null);
1165     else
1166         $fax = null;
1167   }else{
1168     $fax=null;
1169   }
1170   $html='';
1171   if($target_menuid == 'did'){
1172     $fax_dahdi_faxdetect=fax_dahdi_faxdetect();
1173     $fax_sip_faxdetect=fax_sip_faxdetect();
1174     $dahdi=ast_with_dahdi()?_('Dahdi'):_('Zaptel');
1175     $fax_detect=fax_detect();
1176     $fax_settings=fax_get_settings();
1177     //ensure that we are using destination for both fax detect and the regular calls
1178     $html='<script type="text/javascript">$(document).ready(function(){
1179     $("input[name=Submit]").click(function(){
1180       if($("input[name=faxenabled]:checked").val()=="true" && !$("[name=gotoFAX]").val()){//ensure the user selected a fax destination
1181       alert('._('"You have selected Fax Detection on this route. Please select a valid destination to route calls detected as faxes to."').');return false; } }) });</script>';
1182     $html .= '<tr><td colspan="2"><h5>';
1183     $html.=_('Fax Detect');
1184     $html.='<hr></h5></td></tr>';
1185     $html.='<tr>';
1186     $html.='<td><a href="#" class="info">';
1187     $html.=_("Detect Faxes").'<span>'._("Attempt to detect faxes on this DID.")."<ul><li>"._("No: No attempts are made to auto-determine the call type; all calls sent to destination below. Use this option if this DID is used exclusively for voice OR fax.")."</li><li>"._("Yes: try to auto determine the type of call; route to the fax destination if call is a fax, otherwise send to regular destination. Use this option if you receive both voice and fax calls on this line")."</li>";
1188     if($fax_settings['legacy_mode'] == 'yes' || $fax['legacy_email']!==null){
1189       $html.='<li>'._('Legacy: Same as YES, only you can enter an email address as the destination. This option is ONLY for supporting migrated legacy fax routes. You should upgrade this route by choosing YES, and selecting a valid destination!').'</li>';
1190     }
1191     $html.='</ul></span></a>:</td>';
1192     //dont allow detection to be set if we have no valid detection types
1193     if(!$fax_dahdi_faxdetect && !$fax_sip_faxdetect && !$fax_detect['nvfax']){
1194       $js="if ($(this).val() == 'true'){alert('"._('No fax detection methods found or no valid license. Faxing cannot be enabled.')."');return false;}";
1195       $html.='<td><input type="radio" name="faxenabled" value="false" CHECKED />No';
1196       $html.='<input type="radio" name="faxenabled" value="true"  onclick="'.$js.'"/>Yes</td></tr>';
1197       $html.='</table><table>';
1198     }else{
1199       /*
1200        * show detection options
1201        *
1202        * js to show/hide the detection settings. Second slide is always in a
1203        * callback so that we ait for the fits animation to complete before
1204        * playing the second
1205        */
1206       if($fax['legacy_email']===null && $fax_settings['legacy_mode'] == 'no'){
1207         $jsno="$('.faxdetect').slideUp();";
1208         $jsyes="$('.faxdetect').slideDown();";
1209       }else{
1210         $jsno="$('.faxdetect').slideUp();$('.legacyemail').slideUp();";
1211         $jsyes="$('.legacyemail').slideUp('400',function(){
1212               $('.faxdetect').slideDown()
1213             });";
1214         $jslegacy="$('.faxdest27').slideUp('400',function(){
1215                 $('.faxdetect, .legacyemail').not($('.faxdest27')).slideDown();
1216             });";
1217       }
1218       $html.='<td><input type="radio" name="faxenabled" value="false" CHECKED onclick="'.$jsno.'"/>No';
1219       $html.='<input type="radio" name="faxenabled" value="true" '.($fax?'CHECKED':'').' onclick="'.$jsyes.'"/>Yes';
1220       if($fax['legacy_email']!==null || $fax_settings['legacy_mode'] == 'yes'){
1221         $html.='<input type="radio" name="faxenabled" value="legacy"'.($fax['legacy_email'] !== null ? ' CHECKED ':'').'onclick="'.$jslegacy.'"/>Legacy';
1222       }
1223       $html.='</td></tr>';
1224       $html.='</table>';
1225     }
1226     //fax detection+destinations, hidden if there is fax is disabled
1227     $html.='<table class=faxdetect '.($fax?'':'style="display: none;"').'>';
1228     $info=engine_getinfo();
1229     $html.='<tr><td width="156px"><a href="#" class="info">'._('Fax Detection type').'<span>'._("Type of fax detection to use.")."<ul><li>".$dahdi.": "._("use ").$dahdi._(" fax detection; requires 'faxdetect=' to be set to 'incoming' or 'both' in ").$dahdi.".conf</li><li>"._("Sip: use sip fax detection (t38). Requires asterisk 1.6.2 or greater and 'faxdetect=yes' in the sip config files")."</li><li>"._("NV Fax Detect: Use NV Fax Detection; Requires NV Fax Detect to be installed and recognized by asterisk")."</li></ul>".'.</span></a>:</td>';
1230     $html.='<td><select name="faxdetection" tabindex="'.++$tabindex.'">';
1231     //$html.='<option value="Auto"'.($faxdetection == 'auto' ? 'SELECTED' : '').'>'. _("Auto").'</option>';<li>Auto: allow the system to chose the best fax detection method</li>
1232     $html.='<option value="dahdi" '.($fax['detection'] == 'dahdi' ? 'SELECTED' : '').' '.($fax_dahdi_faxdetect?'':'disabled').'>'.$dahdi.'</option>';
1233     $html.='<option value="nvfax"'.($fax['detection'] == 'nvfax' ? 'SELECTED' : '').($fax_detect['nvfax']?'':'disabled').'>'. _("NVFax").'</option>';
1234     $html.='<option value="sip" '.($fax['detection'] == 'sip' ? 'SELECTED' : '').' '.((($info['version'] >= "1.6.2") && $fax_sip_faxdetect)?'':'disabled').'>'. _("Sip").'</option>';
1235     $html.='</select></td></tr>';
1236     
1237     $html.='<tr><td><a href="#" class="info">'._("Fax Detection Time").'<span>'._('How long to wait and try to detect fax. Please note that callers to a '.$dahdi.' channel will hear ringing for this amount of time (i.e. the system wont "answer" the call, it will just play ringing)').'.</span></a>:</td>';
1238     $html.='<td><select name="faxdetectionwait" tabindex="'.++$tabindex.'">';
1239     // ejr 2-17-11 allow zero seconds 
1240     if($fax['detectionwait'] == ''){$fax['detectionwait']=4;}//default wait time is 4 second
1241     
1242     // ejr 2-15-11 allow zero seconds so fax context is created but we don't wait
1243     // important for trunks that dialout and connect - fax detected by dahdi after we go out over trunk - no need to wait here
1244     for($i=0;$i < 11; $i++){
1245       $html.='<option value="'.$i.'" '.($fax['detectionwait']==$i?'SELECTED':'').'>'.$i.'</option>';
1246     }
1247     $html.='</select></td></tr>';
1248     if($fax['legacy_email']!==null || $fax_settings['legacy_mode'] == 'yes'){
1249       $html.='</table>';
1250       $html.='<table class="legacyemail"'.($fax['legacy_email'] === null ? ' style="display: none;"':'').'>';
1251       $html.='<tr ><td><a href="#" class="info">'._("Fax Email Destination").'<span>'._('Address to email faxes to on fax detection.<br />PLEASE NOTE: In this version of FreePBX, you can now set the fax destination from a list of destinations. Extensions/Users can be fax enabled in the user/extension screen and set an email address there. This will create a new destination type that can be selected. To upgrade this option to the full destination list, select YES to Detect Faxes and select a destination. After clicking submit, this route will be upgraded. This Legacy option will no longer be available after the change, it is provided to handle legacy migrations from previous versions of FreePBX only.').'.</span></a>:</td>';
1252       $html.='<td><input name="legacy_email" value="'.$fax['legacy_email'].'"></td></tr>';
1253       $html.='</table>';
1254       $html.='<table class="faxdest27 faxdetect" style="display: none" >';
1255   }
1256     $html.='<tr class="faxdest"><td><a href="#" class="info">'._("Fax Destination").'<span>'._('Where to send the call if we detect that its a fax').'.</span></a>:</td>';
1257     $html.='<td>';
1258     $html.=$fax_detect?drawselects(isset($fax['destination'])?$fax['destination']:null,'FAX',false,false):'';
1259     $html.='</td></tr></table>';
1260     $html.='<table>';
1261   }
1262   return $html;
1263
1264 }
1265     
1266
1267 ?>
Note: See TracBrowser for help on using the browser.