PageRenderTime 46ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/A2Billing_AGI/mode-did.inc.php

https://github.com/xrg/a2billing
PHP | 518 lines | 412 code | 64 blank | 42 comment | 72 complexity | 2cbeb4ed33fe09905229cf840f3cd9d9 MD5 | raw file
Possible License(s): AGPL-1.0
  1. <?php
  2. /** Code for DID mode calls in a2billing AGI
  3. Copyright(C) P. Christeas, Areski, 2007-8
  4. */
  5. /* Retrieve a card structure from the DID engine query row */
  6. function getDIDCard($didrow){
  7. return array( 'id' => $didrow['card_id'],'tgid' =>$didrow['tgid'],
  8. 'username' =>$didrow['username'], 'status'=> $didrow['card_status'],
  9. 'numplan' => $didrow['nplan'], 'useralias' => $didrow['useralias'],
  10. 'features' =>$didrow['features']);
  11. }
  12. /** Mark the 1st-leg call */
  13. function insertCall1($dbh, $card,$did_extension,array $didrow, $last_time,$uniqueid, $agi, $route){
  14. $res = $dbh->Execute('INSERT INTO cc_call (cardid, attempt, cmode, '.
  15. 'sessionid, uniqueid, nasipaddress, src, ' .
  16. 'calledstation, destination, '.
  17. 'srid, brid, tgid, starttime) '.
  18. 'VALUES( ?,?,?,?,?,?,?,?,?,?,?,?,?) RETURNING id;',
  19. array($card['id'],0, 'did',
  20. $agi->request['agi_channel'],$uniqueid,NULL,$agi->request['agi_callerid'],
  21. $did_extension,$route['destination'],
  22. NULL,$didrow['brid2'],$didrow['tgid'],$last_time));
  23. if ($notice = $dbh->NoticeMsg())
  24. $agi->verbose('DB:' . $notice,2);
  25. if (!$res){
  26. $agi->verbose('Cannot mark 1st-l call start in db!');
  27. $agi->conlog($dbh->ErrorMsg(),2);
  28. return false;
  29. }elseif($res->EOF){
  30. $agi->verbose('Cannot mark 1st-l call start in db: EOF!');
  31. return false;
  32. }
  33. $last_call= $res->fetchRow();
  34. $last_call['tcause']=NULL;
  35. $last_call['hupcause']=NULL;
  36. $last_call['cause_ext']=NULL;
  37. $last_call['anstime']=0;
  38. $last_call['dialtime']=0;
  39. return $last_call;
  40. }
  41. function releaseCall1($dbh,&$last_call){
  42. global $agi;
  43. if (isset($last_call['stoptime']))
  44. return;
  45. //try to encode the termination cause..
  46. if ($last_call['tcause']==NULL)
  47. switch($last_call['cause_ext']){
  48. case NULL:
  49. $last_call['tcause']='UNKNOWN';
  50. //$last_call['hupcause']=1;
  51. break;
  52. case 'no-money-':
  53. $last_call['tcause']='UNKNOWN';
  54. //$last_call['hupcause']=1;
  55. break;
  56. // TODO: case 'unreachable':
  57. default:
  58. $agi->conlog("Cannot parse last_prob = \"".$last_call['cause_ext']."\" into proper termination cause.",3);
  59. $last_call['tcause']='UNKNOWN';
  60. //$last_call['hupcause']=1;
  61. break;
  62. }
  63. $res = $dbh->Execute('UPDATE cc_call SET '.
  64. 'stoptime = now(), sessiontime = ?, tcause = ?, hupcause = ?, '.
  65. 'cause_ext =?, startdelay =? '.
  66. /* stopdelay */
  67. 'WHERE id = ? RETURNING stoptime;',
  68. array( $last_call['anstime'],$last_call['tcause'],$last_call['hupcause'],
  69. $last_call['cause_ext'],
  70. ($last_call['dialtime'] - $last_call['anstime']),
  71. $last_call['id']));
  72. if ($notice = $dbh->NoticeMsg())
  73. $agi->verbose('DB:' . $notice,2);
  74. if (!$res){
  75. $agi->verbose('Cannot mark call end in db! (will NOT bill)',0);
  76. $agi->conlog($dbh->ErrorMsg(),2);
  77. return;
  78. }
  79. if ($res->EOF){
  80. $agi->verbose('Call cannot find 1st-l call to release',2);
  81. return;
  82. }
  83. $row=$res->fetchRow();
  84. $last_call['stoptime']=$row['stoptime'];
  85. }
  86. if (getAGIconfig('early_answer',false))
  87. $agi->answer();
  88. else
  89. $agi->exec('Progress');
  90. $did_extension = $agi->request['agi_extension'];
  91. if ($argc > 3 && strlen($argv[3]) > 0 )
  92. $did_code = $argv[3];
  93. else
  94. $did_code = '';
  95. $agi->conlog('DID mode, ext: '.$did_extension .'@'.$did_code,4);
  96. $card=null;
  97. $QRY = str_dbparams($a2b->DBHandle(),'SELECT id(card) AS card_id, tgid, username(card), status(card) AS card_status, ' .
  98. 'nplan, rnplan, useralias(card), features(card), dialstring, dgid, brid2, buyrate2, alert_info, '.
  99. ' now() AS start_time '.
  100. ' FROM DIDEngine(%1, %2, now());',
  101. array($did_extension,$did_code));
  102. $agi->conlog($QRY,3);
  103. $didres = $a2b->DBHandle()->Execute($QRY);
  104. // If the rate engine has anything to Notice/Warn, display that..
  105. // unfortunately, this only tell us the *last* notice
  106. if ($notice = $a2b->DBHandle()->NoticeMsg())
  107. $agi->verbose('DB:' . $notice,2);
  108. if (!$didres){
  109. $agi->verbose('DID engine: query error!',2);
  110. $agi->conlog($a2b->DBHandle()->ErrorMsg(),2);
  111. if(getAGIconfig('say_errors',true))
  112. $agi-> stream_file('allison2'/*-*/, '#');
  113. $card=null;
  114. break;
  115. }elseif($didres->EOF){
  116. $agi->verbose('DID engine: no result.',2);
  117. $agi-> stream_file('prepaid-dest-unreachable', '#');
  118. continue;
  119. }
  120. $num_try=-1; // in a symmetric way, num_try will index the DID rows
  121. $last_call= null; //mark the time the last attempt ended
  122. /* The first level loop: iterate over DID engine results.
  123. It is /very/ hard to directly feed those results in the RateEngine,
  124. because SETOF fns() cannot be fed into function arguments in SQL AFAIK */
  125. while ($didrow = $didres->fetchRow()){
  126. $num_try++;
  127. $agi->conlog(print_r($didrow['card_id'],true),4);
  128. $card= getDIDCard($didrow);
  129. $agi->conlog('Got card: ' . print_r($card,true),4);
  130. if ($card === false)
  131. break;
  132. if ($card === null)
  133. continue;
  134. if($last_call){
  135. if (empty($last_call['cause_ext']))
  136. $last_call['cause_ext']= $last_prob;
  137. releaseCall1($a2b->DBHandle(),$last_call);
  138. }
  139. if ($num_try==0)
  140. $uniqueid=$agi->request['agi_uniqueid'];
  141. else
  142. $uniqueid=$agi->request['agi_uniqueid'].'-'.$num_try;
  143. // Insert an attempt #0 call for the first leg here.
  144. // At early answer the start time should be the AGI starttime.
  145. $last_call=insertCall1($a2b->DBHandle(), $card,$did_extension, $didrow,
  146. ($last_call)? $last_call['stoptime']:$didrow['start_time'],
  147. $uniqueid, $agi,array('destination'=> ''));
  148. if ($last_call===false){
  149. $last_prob='call-insert';
  150. continue;
  151. }
  152. //TODO: fix lang
  153. if ($card['status']!=1){
  154. switch($card['status']){
  155. case 8:
  156. $last_prob = 'card-stopped';
  157. break;
  158. case 5:
  159. $last_prob = 'card-expired';
  160. break;
  161. default:
  162. $last_prob = 'card-status';
  163. $agi->verbose('Card status: '.$card['status'] .', exiting.',2);
  164. }
  165. continue;
  166. }
  167. $card_money = CardGetMoney($card);
  168. if (!$card_money){
  169. ReleaseCard($card);
  170. $card=null;
  171. $last_prob='no-money';
  172. continue;
  173. }
  174. if ($card_money['base'] < getAGIconfig('min_credit_2did',0.00)) {
  175. // not enough money!
  176. $agi->verbose('Not enough money!',2);
  177. $agi->conlog('Money: '. print_r($card_money,true),3);
  178. // Never tell the caller about it!
  179. //$agi->stream_file('prepaid-no-enough-credit','#');
  180. ReleaseCard($card);
  181. $card=null;
  182. $last_prob='no-min-credit';
  183. continue;
  184. }
  185. $QRY = str_dbparams($a2b->DBHandle(),'SELECT * FROM RateEngine2(%#1, %2, %#3, now(), %4);',
  186. array($didrow['tgid'],$didrow['dialstring'],$didrow['nplan'],$card_money['base']));
  187. $agi->conlog($QRY,3);
  188. $res = $a2b->DBHandle()->Execute($QRY);
  189. // If the rate engine has anything to Notice/Warn, display that..
  190. if ($notice = $a2b->DBHandle()->NoticeMsg())
  191. $agi->verbose('DB:' . $notice,2);
  192. if (!$res){
  193. $agi->verbose('Rate engine: query error!',2);
  194. $agi->conlog($a2b->DBHandle()->ErrorMsg(),2);
  195. if(getAGIconfig('say_errors',true))
  196. $agi-> stream_file('allison2'/*-*/, '#');
  197. ReleaseCard($card);
  198. $card=null;
  199. $last_prob='rateengine-fail';
  200. break;
  201. }elseif($res->EOF){
  202. $agi->verbose('Rate engine: no result.',2);
  203. $agi-> stream_file('prepaid-dest-unreachable', '#');
  204. $last_prob='rateengine-norows';
  205. continue;
  206. }
  207. $routes = $res->GetArray();
  208. $agi->conlog('Rate engine: found '.count($routes).' results.',3);
  209. // now, try to reverse translate the CLID we (may) have.
  210. $did_clidreplace=NULL;
  211. if ($didrow['rnplan']){
  212. $QRY = str_dbparams($a2b->DBHandle(), 'SELECT repl,find,length(find) AS find_len,alert_info
  213. FROM cc_re_numplan_pattern
  214. WHERE cc_re_numplan_pattern.nplan = %#1
  215. AND ( cc_re_numplan_pattern.fplan IS NULL OR cc_re_numplan_pattern.fplan = %#2)
  216. AND ( %3 LIKE cc_re_numplan_pattern.find || \'%%\')
  217. ORDER BY length(find) DESC LIMIT 1;',
  218. array($didrow['rnplan'],$didrow['nplan'],$agi->request['agi_callerid']));
  219. $agi->conlog($QRY,3);
  220. $res = $a2b->DBHandle()->Execute($QRY);
  221. // If the rate engine has anything to Notice/Warn, display that..
  222. if ($notice = $a2b->DBHandle()->NoticeMsg())
  223. $agi->verbose('DB:' . $notice,2);
  224. if (!$res){
  225. $agi->verbose('CLID query: query error!',2);
  226. $agi->conlog($a2b->DBHandle()->ErrorMsg(),2);
  227. if(getAGIconfig('say_errors',true))
  228. $agi-> stream_file('allison2'/*-*/, '#');
  229. }elseif($res->EOF){
  230. $agi->verbose('CLID query: no result.',2);
  231. }else {
  232. $did_clidreplace = $res->fetchRow();
  233. $agi->conlog('CLID query: '.print_r($did_clidreplace,true),3);
  234. }
  235. }
  236. $did_clidname = $agi->request['agi_calleridname'];
  237. if (empty($agi->request['agi_calleridname'])|| true || (preg_match('/\^+?[0-9]*$/',$agi->request['agi_calleridname'])>=1)) {
  238. $QRY = str_dbparams($a2b->DBHandle(), 'SELECT did_pb_entry.name
  239. FROM did_pb_entry, did_phonebook, cc_card
  240. WHERE did_pb_entry.pb = did_phonebook.id
  241. AND ( did_phonebook.rnplan IS NULL OR did_phonebook.rnplan = %#1)
  242. AND ( did_phonebook.card_group IS NULL OR did_phonebook.card_group = cc_card.grp)
  243. AND ( did_phonebook.cardid IS NULL OR did_phonebook.cardid = %#3)
  244. AND cc_card.id = %#3
  245. AND did_pb_entry.dnum = %!4;',
  246. array($didrow['rnplan'], null, $card['id'],$agi->request['agi_callerid']));
  247. $agi->conlog($QRY,3);
  248. $res = $a2b->DBHandle()->Execute($QRY);
  249. // If the rate engine has anything to Notice/Warn, display that..
  250. if ($notice = $a2b->DBHandle()->NoticeMsg())
  251. $agi->verbose('DB:' . $notice,2);
  252. if (!$res){
  253. $agi->verbose('CLID pb query: query error!',2);
  254. $agi->conlog($a2b->DBHandle()->ErrorMsg(),2);
  255. if(getAGIconfig('say_errors',true))
  256. $agi-> stream_file('allison2'/*-*/, '#');
  257. }elseif($res->EOF){
  258. $agi->verbose('CLID pb query: no result.',2);
  259. }else {
  260. $cldrow=$res->fetchRow();
  261. $did_clidname = $cldrow['name'];
  262. $agi->conlog('CLID query: '.print_r($cldrow,true),3);
  263. }
  264. }
  265. try {
  266. $attempt = 1;
  267. $last_prob = '';
  268. $special_only = false;
  269. foreach ($routes as $route){
  270. if ($route['tmout'] < getAGIconfig('min_duration_2did',30)){
  271. $agi->conlog('Call will be too short: ',$route['tmout'],3);
  272. $last_prob = 'min-length';
  273. continue;
  274. }
  275. if ($route['tmout'] > getAGIconfig('max_did_duration',604800)){
  276. $route['tmout'] = getAGIconfig('max_did_duration',604800);
  277. $agi->conlog('Call truncated to: ',$route['tmout'],3);
  278. }
  279. // Check if trunk needs a feature subscription
  280. if(!empty($route['trunkfeat'])){
  281. // This field comes as a string, convert to array..
  282. if (!empty($card['features']) && !is_array($card['features']))
  283. $card['features']= sql_decodeArray($card['features']);
  284. if (empty($card['features']) || !in_array($route['trunkfeat'],$card['features'])){
  285. if (empty($last_prob))
  286. $last_prob='no-feature';
  287. $agi->conlog("Call is missing feature \"".$route['trunkfeat']."\", skipping route.",3);
  288. $agi->conlog("Features: ".print_r($card['features'],true),4);
  289. continue;
  290. }
  291. // feature found!
  292. $agi->conlog('Call using feature: '.$route['trunkfeat'],4);
  293. }
  294. $dialstr = formatDialstring($didrow['dialstring'],$route, $card);
  295. if ($special_only && ($dialstr !==true))
  296. continue;
  297. $route['call_uniqueid']=$uniqueid;
  298. if ($dialstr === null){
  299. $last_prob='unreachable';
  300. continue;
  301. }elseif (!$dialstr){
  302. $last_prob='no-dialstring';
  303. continue;
  304. }elseif($dialstr ===true){
  305. if ($did_clidname != $agi->request['agi_calleridname'])
  306. $agi->set_variable('CALLERID(name)',$did_clidname);
  307. if (dialSpecial($didrow['dialstring'],$route, $card,$card_money,$last_prob,$agi,$attempt))
  308. break;
  309. else
  310. continue;
  311. }
  312. // Callerid
  313. if ($did_clidreplace !== NULL){
  314. $new_clid = str_alparams($did_clidreplace['repl'],
  315. array( useralias =>$card['useralias'],
  316. nplan => $card['numplan'],
  317. callernum => $agi->request['agi_callerid'],
  318. callern => substr($agi->request['agi_callerid'],$did_clidreplace['find_len'])));
  319. }else
  320. $new_clid = $agi->request['agi_callerid'];
  321. // we always reset the clid, because the previous rate
  322. // engine may have changed it.
  323. $agi->conlog("Setting clid to : \"$did_clidname\" <$new_clid>",3);
  324. if ($did_clidname != $agi->request['agi_calleridname'])
  325. $agi->set_variable('CALLERID(name)',$did_clidname);
  326. $agi->set_variable('CALLERID(num)',$new_clid);
  327. $res = $a2b->DBHandle()->Execute('INSERT INTO cc_call (cardid, attempt, cmode, '.
  328. 'sessionid, uniqueid, nasipaddress, src, ' .
  329. 'calledstation, destination, '.
  330. 'srid, brid, tgid, trunk) '.
  331. 'VALUES( ?,?,?,?,?,?,?,?,?,?,?,?,?) RETURNING id;',
  332. array($card['id'],$attempt, 'did',
  333. $agi->request['agi_channel'],$uniqueid,NULL,$agi->request['agi_callerid'],
  334. $did_extension,$route['destination'],
  335. $route['srid'],$route['brid'],$didrow['tgid'],$route['trunkid']));
  336. if ($notice = $a2b->DBHandle()->NoticeMsg())
  337. $agi->verbose('DB:' . $notice,2);
  338. if (!$res){
  339. $agi->verbose('Cannot mark call start in db!');
  340. $agi->conlog($a2b->DBHandle()->ErrorMsg(),2);
  341. // This error may mean that trunk is in use etc.
  342. // If call cannot be billed, we'd better abort it.
  343. $last_prob='call-insert';
  344. continue;
  345. }elseif($res->EOF){
  346. $agi->verbose('Cannot mark call start in db: EOF!');
  347. $last_prob='call-insert';
  348. continue;
  349. }
  350. $call_id = $res->fetchRow();
  351. $agi->conlog('Start call '. $call_id['id'],4);
  352. if ($did_clidreplace && !empty($did_clidreplace['alert_info'])){
  353. $agi->conlog('Setting DID rn alert to :'. $did_clidreplace['alert_info'],4);
  354. $agi->exec('SIPAddHeader','Alert-Info:'.$did_clidreplace['alert_info']);
  355. }else if (!empty($didrow['alert_info'])){
  356. $agi->conlog('Setting DID alert to :'. $didrow['alert_info']);
  357. $agi->exec('SIPAddHeader','Alert-Info:'.$didrow['alert_info']);
  358. }elseif (!empty($route['alert_info'])){
  359. //$agi->conlog('Setting alert to :'. $route['alert_info']);
  360. $agi->exec('SIPAddHeader','Alert-Info:'.$route['alert_info']);
  361. }
  362. $agi->conlog("Dial '". $route['destination']. "'@". $route['trunkcode'] . " : $dialstr",3);
  363. $attempt++;
  364. $call_res= $agi->exec('Dial',$dialstr);
  365. //TODO: if record, stop
  366. $hangupcause=$agi->get_variable('HANGUPCAUSE');
  367. $answeredtime = $agi->get_variable("ANSWEREDTIME");
  368. if (($answeredtime['result']== 0) || empty($answeredtime['data']))
  369. $answeredtime['data'] =0;
  370. $dialstatus = $agi->get_variable("DIALSTATUS");
  371. $dialedtime = $agi->get_variable("DIALEDTIME");
  372. if ($dialedtime['result']== 0)
  373. $dialedtime['data'] =0;
  374. $agi->conlog("Dial result: ".$dialstatus['data'].'('. $hangupcause['data']. ') after '. $answeredtime['data'].'sec.',2);
  375. //$agi->conlog("After dial, answertime: ".print_r($answeredtime,true));
  376. //TODO: SIP, ISDN extended status
  377. $can_continue = true;
  378. $cause_ext = '';
  379. switch ($dialstatus['data']){
  380. case 'BUSY':
  381. $last_prob='busy';
  382. $special_only=true;
  383. break;
  384. case 'ANSWERED':
  385. case 'ANSWER':
  386. $can_continue=false;
  387. $last_prob='';
  388. break;
  389. case 'CANCEL':
  390. $special_only=true;
  391. $last_prob='cancel';
  392. break;
  393. case 'CONGESTION':
  394. case 'CHANUNAVAIL':
  395. $last_prob='call-fail';
  396. break;
  397. case 'NOANSWER':
  398. $last_prob='no-answer';
  399. break;
  400. default:
  401. $agi->verbose("Unknown status: ".$dialstatus['data'],2);
  402. $special_only=true;
  403. }
  404. // store them in last_call
  405. $last_call['tcause']=$dialstatus['data'];
  406. $last_call['hupcause']=$hangupcause['data'];
  407. $last_call['cause_ext']=$last_prob;
  408. if ($last_call['anstime']==0)
  409. $last_call['anstime']=$answeredtime['data'];
  410. else
  411. $last_call['anstime']+=$dialedtime['data'];
  412. $last_call['dialtime']+=$dialedtime['data'];
  413. $res = $a2b->DBHandle()->Execute('UPDATE cc_call SET '.
  414. 'stoptime = now(), sessiontime = ?, tcause = ?, hupcause = ?, '.
  415. 'cause_ext =?, startdelay =? '.
  416. /* stopdelay */
  417. 'WHERE id = ? ;',
  418. array( $answeredtime['data'],$dialstatus['data'],$hangupcause['data'],
  419. $cause_ext,
  420. ($dialedtime['data'] - $answeredtime['data']),
  421. $call_id['id']));
  422. if ($notice = $a2b->DBHandle()->NoticeMsg())
  423. $agi->verbose('DB:' . $notice,2);
  424. if (!$res){
  425. $agi->verbose('Cannot mark call end in db! (will NOT bill)',0);
  426. $agi->conlog($a2b->DBHandle()->ErrorMsg(),2);
  427. }
  428. if (!$can_continue) //TODO: manual dialnum?
  429. break;
  430. } //for
  431. }catch (Exception $ex){
  432. // Here we handle signals received
  433. $agi->verbose("Exception at dial:". $ex->getMessage());
  434. @syslog("Exception at dial:". $ex->getMessage());
  435. ReleaseCard($card);
  436. $card=null;
  437. break;
  438. }
  439. } // while
  440. //TODO: set hangup cause accordingly
  441. if ($last_prob)
  442. $agi->conlog("Last problem: ".$last_prob,2);
  443. if ($card && !empty($card['locked']))
  444. ReleaseCard($card);
  445. if($last_call){
  446. if (empty($last_call['cause_ext']))
  447. $last_call['cause_ext']= $last_prob;
  448. releaseCall1($a2b->DBHandle(),$last_call);
  449. }
  450. $agi->conlog('Goodbye!',3);
  451. if(getAGIconfig('say_did_goodbye',false) && $agi->is_alive)
  452. $agi-> stream_file('prepaid-final', '#');
  453. $agi->hangup();
  454. ?>