api.telepony.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
  1. <?php
  2. /**
  3. * Uncomplicated telephony class
  4. */
  5. class TelePony {
  6. /**
  7. * Contains system alter.ini config as key=>value
  8. *
  9. * @var array
  10. */
  11. protected $altCfg = array();
  12. /**
  13. * Contains system billing.ini config as key=>value
  14. *
  15. * @var array
  16. */
  17. protected $billCfg = array();
  18. /**
  19. * All available phonebook contacts as number=>contact
  20. *
  21. * @var array
  22. */
  23. protected $allContacts = array();
  24. /**
  25. * System messages helper paceholder
  26. *
  27. * @var object
  28. */
  29. protected $messages = '';
  30. /**
  31. * Some predefined routes, URLs, paths etc..
  32. */
  33. const URL_ME = '?module=telepony';
  34. const ROUTE_AJCALLSHIST = 'ajaxcallshist';
  35. const ROUTE_INCALLSTATS = 'incallstats';
  36. const ROUTE_CALLSHIST = 'callshistory';
  37. const ROUTE_NIGHTCALLS = 'nightcalls';
  38. const ROUTE_DFROM = 'datefrom';
  39. const ROUTE_DTO = 'dateto';
  40. const ROUTE_DLOADCDR = 'downloadcdr';
  41. const PATH_CDRDEBUG = 'exports/teleponycdrdebug.log';
  42. /**
  43. * Creates new TelePony instance
  44. *
  45. * @return void
  46. */
  47. public function __construct() {
  48. $this->loadConfigs();
  49. $this->initMessages();
  50. }
  51. /**
  52. * Inits system messages helper
  53. *
  54. * @return void
  55. */
  56. protected function initMessages() {
  57. $this->messages = new UbillingMessageHelper();
  58. }
  59. /**
  60. * Preloads required configs into protected properties
  61. *
  62. * @global object $ubillingConfig
  63. *
  64. * @return void
  65. */
  66. protected function loadConfigs() {
  67. global $ubillingConfig;
  68. $this->altCfg = $ubillingConfig->getAlter();
  69. $this->billCfg = $ubillingConfig->getBilling();
  70. }
  71. /**
  72. * Renders basic module controls
  73. *
  74. * @return string
  75. */
  76. public function renderControls() {
  77. $result = '';
  78. $result .= wf_Link(self::URL_ME . '&' . self::ROUTE_INCALLSTATS . '=true', wf_img('skins/icon_stats_16.gif') . ' ' . __('Stats') . ' ' . __('Incoming calls'), false, 'ubButton') . ' ';
  79. if ($this->altCfg['TELEPONY_CDR']) {
  80. $result .= wf_Link(self::URL_ME . '&' . self::ROUTE_CALLSHIST . '=true', wf_img('skins/icon_cdr.png') . ' ' . __('Calls history'), false, 'ubButton') . ' ';
  81. $result .= wf_Link(self::URL_ME . '&' . self::ROUTE_NIGHTCALLS . '=true', wf_img('skins/icon_moon.png') . ' ' . __('Calls during non-business hours'), false, 'ubButton') . ' ';
  82. }
  83. return($result);
  84. }
  85. /**
  86. * Renders incoming calls stats if it exists
  87. *
  88. * @return string
  89. */
  90. public function renderNumLog() {
  91. $logPath = PBXNum::LOG_PATH;
  92. $catPath = $this->billCfg['CAT'];
  93. $grepPath = $this->billCfg['GREP'];
  94. $replyOffset = 5;
  95. $numberOffset = 2;
  96. $loginOffset = 7;
  97. $replyCount = 0;
  98. $replyStats = array();
  99. $replyNames = array(
  100. 0 => __('Not found'),
  101. 1 => __('Active'),
  102. 2 => __('Debt'),
  103. 3 => __('Frozen')
  104. );
  105. $result = '';
  106. if (file_exists($logPath)) {
  107. if (!wf_CheckPost(array('numyear', 'nummonth'))) {
  108. $curYear = curyear();
  109. $curMonth = date("m");
  110. } else {
  111. $curYear = ubRouting::post('numyear', 'int');
  112. $curMonth = ubRouting::post('nummonth', 'int');
  113. }
  114. $parseDate = $curYear . '-' . $curMonth;
  115. $dateInputs = wf_YearSelectorPreset('numyear', __('Year'), false, $curYear) . ' ';
  116. $dateInputs .= wf_MonthSelector('nummonth', __('Month'), $curMonth, false) . ' ';
  117. $dateInputs .= wf_Submit(__('Show'));
  118. $result .= wf_Form('', 'POST', $dateInputs, 'glamour');
  119. $rawLog = shell_exec($catPath . ' ' . $logPath . ' | ' . $grepPath . ' ' . $parseDate . '-');
  120. if (!empty($rawLog)) {
  121. $rawLog = explodeRows($rawLog);
  122. if (!empty($rawLog)) {
  123. foreach ($rawLog as $io => $each) {
  124. if (!empty($each)) {
  125. $line = explode(' ', $each);
  126. $callReply = $line[$replyOffset];
  127. if (isset($replyStats[$callReply])) {
  128. $replyStats[$callReply] ++;
  129. } else {
  130. $replyStats[$callReply] = 1;
  131. }
  132. $replyCount++;
  133. }
  134. }
  135. if (!empty($replyStats)) {
  136. $cells = wf_TableCell(__('Calls'));
  137. $cells .= wf_TableCell(__('Count'));
  138. $rows = wf_TableRow($cells, 'row1');
  139. foreach ($replyStats as $replyCode => $callsCount) {
  140. $cells = wf_TableCell($replyNames[$replyCode]);
  141. $cells .= wf_TableCell($callsCount);
  142. $rows .= wf_TableRow($cells, 'row5');
  143. }
  144. $result .= wf_TableBody($rows, '100%', 0, 'sortable');
  145. $result .= __('Total') . ': ' . $replyCount;
  146. }
  147. }
  148. } else {
  149. $result .= $this->messages->getStyledMessage(__('Nothing to show'), 'warning');
  150. }
  151. } else {
  152. $result .= $this->messages->getStyledMessage(__('File not exist') . ': ' . $logPath, 'error');
  153. }
  154. return($result);
  155. }
  156. /**
  157. * Returns raw CDR for selected period of time
  158. *
  159. * @param string $cdrConf
  160. * @param string $dateFrom
  161. * @param string $dateTo
  162. *
  163. * @return array/bool
  164. */
  165. public function getCDR($dateFrom = '', $dateTo = '') {
  166. $result = array();
  167. $cdrConf = $this->altCfg['TELEPONY_CDR'];
  168. if (!empty($cdrConf)) {
  169. $cdrConf = explode('|', $cdrConf);
  170. if (sizeof($cdrConf) == 5) {
  171. if ($dateFrom AND $dateTo) {
  172. $dateFrom .= ' 00:00:00';
  173. $dateTo .= ' 23:59:59';
  174. }
  175. $host = $cdrConf[0];
  176. $login = $cdrConf[1];
  177. $password = $cdrConf[2];
  178. $db = $cdrConf[3];
  179. $table = $cdrConf[4];
  180. $cdr = new PBXCdr($host, $login, $password, $db, $table);
  181. $result = $cdr->getCDR($dateFrom, $dateTo);
  182. } else {
  183. $result = false;
  184. }
  185. } else {
  186. $result = false;
  187. }
  188. return($result);
  189. }
  190. /**
  191. * Groups all calls from CDR as uniqueFlowId=>call records
  192. *
  193. * @param array $rawCdr
  194. *
  195. * @return array
  196. */
  197. protected function groupCDRflows($rawCdr) {
  198. $result = array();
  199. if (!empty($rawCdr)) {
  200. foreach ($rawCdr as $io => $each) {
  201. $flowId = $each['uniqueid'];
  202. $result[$flowId][] = $each;
  203. }
  204. }
  205. return($result);
  206. }
  207. /**
  208. * Parses channel data to extract peer number
  209. *
  210. * @param string $channel
  211. *
  212. * @return int
  213. */
  214. protected function parseChannel($channel) {
  215. $result = '';
  216. if (!empty($channel)) {
  217. $cleanChan = str_replace('SIP/', '', $channel);
  218. $explodedChan = explode('-', $cleanChan);
  219. if (isset($explodedChan[1])) {
  220. $result = $explodedChan[0];
  221. }
  222. }
  223. return($result);
  224. }
  225. /**
  226. * Parses flow data into humanic stats array.
  227. * Fields: flowid, callstart, callend, from, to, records, direction,
  228. * status,duration,realtime,takephone, context, app
  229. *
  230. * @param array $flowData
  231. *
  232. * @return string
  233. */
  234. protected function parseCDRFlow($flowData) {
  235. $result = array();
  236. if (!empty($flowData)) {
  237. $initialRecord = $flowData[0];
  238. $finalRecord = end($flowData);
  239. $recordCount = sizeof($flowData);
  240. $result['flowid'] = $initialRecord['uniqueid'];
  241. $result['callstart'] = $initialRecord['calldate'];
  242. $result['callend'] = $finalRecord['calldate'];
  243. $result['from'] = $initialRecord['src'];
  244. $destination = $finalRecord['dst'];
  245. //fuck this shit
  246. if ($destination == 's') {
  247. $destination = $this->parseChannel($finalRecord['dstchannel']);
  248. }
  249. $result['to'] = $destination;
  250. $result['records'] = $recordCount;
  251. $result['direction'] = $finalRecord['userfield'];
  252. $result['status'] = $finalRecord['disposition'];
  253. $result['duration'] = $finalRecord['duration'];
  254. $result['realtime'] = 0;
  255. $result['takephone'] = '';
  256. if ($finalRecord['disposition'] == 'ANSWERED') {
  257. $result['realtime'] = $finalRecord['billsec'];
  258. $result['takephone'] = $destination;
  259. }
  260. $result['context'] = $finalRecord['dcontext'];
  261. $result['app'] = $finalRecord['lastapp'];
  262. }
  263. return($result);
  264. }
  265. /**
  266. * Returns styled call status lable depend of disposiotion
  267. *
  268. * @param string $rawStatus
  269. *
  270. * @return string
  271. */
  272. protected function renderCallStatus($rawStatus) {
  273. $result = '';
  274. $callStatus = '';
  275. $statusIcon = '';
  276. switch ($rawStatus) {
  277. case 'ANSWERED':
  278. $callStatus = __('Answered');
  279. $statusIcon = wf_img('skins/calls/phone_green.png');
  280. break;
  281. case 'NO ANSWER':
  282. $callStatus = __('No answer');
  283. $statusIcon = wf_img('skins/calls/phone_red.png');
  284. break;
  285. case 'BUSY':
  286. $callStatus = __('Busy');
  287. $statusIcon = wf_img('skins/calls/phone_yellow.png');
  288. break;
  289. case 'FAILED':
  290. $callStatus = __('Failed');
  291. $statusIcon = wf_img('skins/calls/phone_fail.png');
  292. break;
  293. default :
  294. $callStatus = $rawStatus;
  295. break;
  296. }
  297. $result = $statusIcon . ' ' . $callStatus;
  298. return($result);
  299. }
  300. /**
  301. * Returns some contact if available in phonebook
  302. *
  303. * @param string $number
  304. *
  305. * @return string
  306. */
  307. protected function renderNumber($number) {
  308. $result = $number;
  309. if (!empty($number)) {
  310. if (isset($this->allContacts[$number])) {
  311. $result .= ' - ' . $this->allContacts[$number];
  312. }
  313. }
  314. return($result);
  315. }
  316. /**
  317. * Renders CDR date selection form
  318. *
  319. * @return string
  320. */
  321. public function renderCdrDateForm() {
  322. $inputs = '';
  323. if (ubRouting::checkPost(array(self::ROUTE_DFROM, self::ROUTE_DTO))) {
  324. $dateFrom = ubRouting::post(self::ROUTE_DFROM);
  325. $dateTo = ubRouting::post(self::ROUTE_DTO);
  326. } else {
  327. $dateFrom = curdate();
  328. $dateTo = curdate();
  329. }
  330. $inputs .= wf_DatePickerPreset(self::ROUTE_DFROM, $dateFrom) . ' ' . __('From') . ' ';
  331. $inputs .= wf_DatePickerPreset(self::ROUTE_DTO, $dateTo) . ' ' . __('To') . ' ';
  332. $inputs .= wf_Submit(__('Show'));
  333. $result = wf_Form('', 'POST', $inputs, 'glamour');
  334. return ($result);
  335. }
  336. /**
  337. * Renders calls history data container
  338. *
  339. * @return void
  340. */
  341. public function renderCDR() {
  342. $result = '';
  343. if (ubRouting::checkPost(array(self::ROUTE_DFROM, self::ROUTE_DTO))) {
  344. $dateFrom = ubRouting::post(self::ROUTE_DFROM);
  345. $dateTo = ubRouting::post(self::ROUTE_DTO);
  346. } else {
  347. $dateFrom = curdate();
  348. $dateTo = curdate();
  349. }
  350. $columns = array('#', __('Direction'), __('Time'), __('From'), __('To'), __('Picked up'), __('Type'), __('Status'), __('Talk time'));
  351. $opts = '"order": [[ 0, "desc" ]]';
  352. $ajDataUrl = self::URL_ME . '&' . self::ROUTE_CALLSHIST . '=true' . '&' . self::ROUTE_AJCALLSHIST . '=true';
  353. $ajDataUrl .= '&' . self::ROUTE_DFROM . '=' . $dateFrom . '&' . self::ROUTE_DTO . '=' . $dateTo;
  354. $result .= wf_JqDtLoader($columns, $ajDataUrl, false, __('Calls'), 50, $opts);
  355. if (cfr('ROOT')) {
  356. if (file_exists(self::PATH_CDRDEBUG)) {
  357. $result .= wf_delimiter(0);
  358. $dlUrl = self::URL_ME . '&' . self::ROUTE_CALLSHIST . '=true' . '&' . self::ROUTE_DLOADCDR . '=true';
  359. $result .= wf_Link($dlUrl, wf_img('skins/icon_download.png') . ' ' . __('Download') . ' ' . __('CDR'), false, 'ubButton');
  360. }
  361. }
  362. return($result);
  363. }
  364. /**
  365. * Renders some calls history JSON data
  366. *
  367. * @return void
  368. */
  369. public function getCDRJson() {
  370. $result = '';
  371. $dateFrom = ubRouting::get('datefrom');
  372. $dateTo = ubRouting::get('dateto');
  373. $rawCdr = $this->getCDR($dateFrom, $dateTo);
  374. $json = new wf_JqDtHelper();
  375. //preload phonebook contacts
  376. if ($this->altCfg['PHONEBOOK_ENABLED']) {
  377. $phoneBook = new PhoneBook();
  378. $this->allContacts = $phoneBook->getAllContacts();
  379. }
  380. if ($rawCdr !== false) {
  381. $flows = array();
  382. if (!empty($rawCdr)) {
  383. $flows = $this->groupCDRflows($rawCdr);
  384. $flowCounter = 0;
  385. if (!empty($flows)) {
  386. foreach ($flows as $eachFlowId => $eachFlowData) {
  387. $flowCounter++;
  388. $callData = $this->parseCDRFlow($eachFlowData);
  389. $callDirection = '';
  390. //setting call direction icon
  391. if ($callData['direction'] == 'in' OR $callData['app'] == 'Queue') {
  392. $callDirection = wf_img('skins/calls/incoming.png').' '.__('incoming call');
  393. } else {
  394. $callDirection = wf_img('skins/calls/outgoing.png').' '.__('outgoing call');
  395. }
  396. $data[] = $flowCounter;
  397. $data[] = $callDirection;
  398. $data[] = $callData['callstart'];
  399. $data[] = $this->renderNumber($callData['from']);
  400. $data[] = $this->renderNumber($callData['to']);
  401. $data[] = $this->renderNumber($callData['takephone']);
  402. $data[] = __($callData['app']);
  403. $data[] = $this->renderCallStatus($callData['status']);
  404. $data[] = zb_formatTime($callData['realtime']);
  405. $json->addRow($data);
  406. unset($data);
  407. }
  408. }
  409. }
  410. //writing debug log
  411. file_put_contents(self::PATH_CDRDEBUG, print_r($rawCdr, true));
  412. }
  413. $json->getJson();
  414. }
  415. /**
  416. * Fetches and preprocess some missed calls from CDR
  417. *
  418. * @return array
  419. */
  420. public function fetchMissedCalls() {
  421. $result = array(
  422. 'unanswered' => array(),
  423. 'recalled' => array()
  424. );
  425. $minNumLen = 9;
  426. $countryCode = '380';
  427. $codeCutNum = 2; //number of leading digits to cut from country code
  428. $unansweredCalls = array();
  429. $recalledCalls = array();
  430. $missedTries = array();
  431. $callsTmp = array();
  432. $normalCalls = array();
  433. $firstRecallTime = array(); //contains first recall start times as destNum=>time
  434. //cut substr
  435. $cutSubstr = '';
  436. if (!empty($countryCode) AND $codeCutNum) {
  437. $cutSubstr = substr($countryCode, 0, $codeCutNum);
  438. }
  439. $rawCalls = $this->getCDR(curdate(), curdate());
  440. if (!empty($rawCalls)) {
  441. $normalCalls = $this->groupCDRflows($rawCalls);
  442. }
  443. if (!empty($normalCalls)) {
  444. foreach ($normalCalls as $io => $each) {
  445. $callData = $this->parseCDRFlow($each);
  446. $startTime = $callData['callstart'];
  447. $incomingNumber = $callData['from'];
  448. $destinationNumber = $callData['to'];
  449. //number cleanup required?
  450. if (!empty($cutSubstr)) {
  451. if (ispos($incomingNumber, $countryCode)) {
  452. $incomingNumber = str_replace($cutSubstr, '', $incomingNumber);
  453. }
  454. if (ispos($destinationNumber, $countryCode)) {
  455. $destinationNumber = str_replace($cutSubstr, '', $destinationNumber);
  456. }
  457. }
  458. //not answered call
  459. if ($callData['status'] != 'ANSWERED' AND $callData['app'] == 'Queue') {
  460. //excluding internal numbers
  461. if (strlen((string) $incomingNumber) >= $minNumLen) {
  462. $unansweredCalls[$incomingNumber] = $callData;
  463. $unansweredCalls[$incomingNumber][9] = $startTime; //last time compat
  464. //unanswered calls count
  465. if (isset($missedTries[$incomingNumber])) {
  466. $missedTries[$incomingNumber] ++;
  467. } else {
  468. $missedTries[$incomingNumber] = 1;
  469. }
  470. }
  471. }
  472. //incoming call answered after miss
  473. if (isset($unansweredCalls[$incomingNumber])) {
  474. if ($callData['app'] == 'Queue' AND $callData['status'] == 'ANSWERED') {
  475. unset($unansweredCalls[$incomingNumber]);
  476. }
  477. }
  478. //recall try on missed number
  479. if (isset($unansweredCalls[$destinationNumber])) {
  480. $reactionTime = 0;
  481. $missTime = $unansweredCalls[$destinationNumber]['callend'];
  482. $missTime = strtotime($missTime);
  483. $startTimeStamp = strtotime($startTime);
  484. //first recall try time here
  485. if ($startTimeStamp > $missTime) {
  486. if (!isset($firstRecallTime[$destinationNumber])) {
  487. $firstRecallTime[$destinationNumber] = $startTimeStamp;
  488. }
  489. } else {
  490. unset($firstRecallTime[$destinationNumber]);
  491. }
  492. //Yeah, seems we recalled missed number and he answered
  493. if ($callData['status'] == 'ANSWERED') {
  494. unset($unansweredCalls[$destinationNumber]);
  495. $answerTime = strtotime($callData['callstart']);
  496. $recallTryTime = (isset($firstRecallTime[$destinationNumber])) ? $firstRecallTime[$destinationNumber] : strtotime($startTime);
  497. $reactionTime = $recallTryTime - $missTime;
  498. $recalledCalls[$destinationNumber]['time'] = $callData['realtime'];
  499. if (isset($recalledCalls[$destinationNumber]['count'])) {
  500. $recalledCalls[$destinationNumber]['count'] ++;
  501. } else {
  502. $recalledCalls[$destinationNumber]['count'] = 1;
  503. }
  504. $recalledCalls[$destinationNumber]['trytime'] = $reactionTime;
  505. } else {
  506. //We tried but without success
  507. $recallTryTime = (isset($firstRecallTime[$destinationNumber])) ? $firstRecallTime[$destinationNumber] : strtotime($startTime);
  508. $reactionTime = $recallTryTime - $missTime; //time of try to recall
  509. $recalledCalls[$destinationNumber]['time'] = 0;
  510. @$recalledCalls[$destinationNumber]['count'] ++;
  511. @$recalledCalls[$destinationNumber]['trytime'] = $reactionTime;
  512. }
  513. }
  514. //Unknown numbers not require recall
  515. if (ispos($incomingNumber, 'nknown') OR ispos($incomingNumber, 'nonymous')) {
  516. unset($unansweredCalls[$incomingNumber]);
  517. }
  518. }
  519. }
  520. //appending tries to final result
  521. if (!empty($missedTries)) {
  522. foreach ($missedTries as $missedNumber => $missCount) {
  523. if (isset($unansweredCalls[$missedNumber])) {
  524. $unansweredCalls[$missedNumber]['misscount'] = $missCount;
  525. }
  526. }
  527. }
  528. $result['unanswered'] = $unansweredCalls;
  529. $result['recalled'] = $recalledCalls;
  530. return($result);
  531. }
  532. /**
  533. * Returns calls stats data for current month for the exhorse
  534. *
  535. * @return array
  536. */
  537. public function getHorseMonthData() {
  538. $result = array(
  539. 'a_totalcalls' => 0,
  540. 'a_totalanswered' => 0,
  541. 'a_totalcallsduration' => 0,
  542. 'a_averagecallduration' => 0,
  543. 'a_outtotalcalls' => 0,
  544. 'a_outtotalanswered' => 0,
  545. 'a_outtotalcallsduration' => 0,
  546. 'a_outaveragecallduration' => 0,
  547. );
  548. //working time setup
  549. $rawWorkTime = $this->altCfg['WORKING_HOURS'];
  550. $rawWorkTime = explode('-', $rawWorkTime);
  551. $workStartTime = $rawWorkTime[0];
  552. $workEndTime = $rawWorkTime[1];
  553. $rawCdr = $this->getCDR(curmonth() . '-01', curdate());
  554. if ($rawCdr !== false) {
  555. if (!empty($rawCdr)) {
  556. $normalCalls = $this->groupCDRflows($rawCdr);
  557. foreach ($normalCalls as $eachFlow => $flowData) {
  558. $callData = $this->parseCDRFlow($flowData);
  559. //Only incoming calls
  560. if ($callData['direction'] == 'in' OR $callData['app'] == 'Queue') {
  561. $callStartTime = $callData['callstart'];
  562. //Only work time
  563. if (zb_isTimeBetween($workStartTime, $workEndTime, $callStartTime)) {
  564. $result['a_totalcalls'] ++;
  565. $result['a_totalcallsduration'] += $callData['realtime'];
  566. if ($callData['status'] == 'ANSWERED') {
  567. $result['a_totalanswered'] ++;
  568. }
  569. }
  570. } else {
  571. if ($callData['direction'] == 'out') {
  572. $callStartTime = $callData['callstart'];
  573. //Only work time
  574. if (zb_isTimeBetween($workStartTime, $workEndTime, $callStartTime)) {
  575. $result['a_outtotalcalls'] ++;
  576. $result['a_outtotalcallsduration'] += $callData['realtime'];
  577. if ($callData['status'] == 'ANSWERED') {
  578. $result['a_outtotalanswered'] ++;
  579. }
  580. }
  581. }
  582. }
  583. }
  584. //prevent division by zero on no answered incoming calls
  585. if ($result['a_totalanswered'] != 0) {
  586. $result['a_averagecallduration'] = $result['a_totalcallsduration'] / $result['a_totalanswered'];
  587. }
  588. if ($result['a_outtotalanswered'] != 0) {
  589. $result['a_outaveragecallduration'] = $result['a_outtotalcallsduration'] / $result['a_outtotalanswered'];
  590. }
  591. }
  592. }
  593. return($result);
  594. }
  595. /**
  596. * Renders calls during non-business hours list
  597. *
  598. * @return string
  599. */
  600. public function renderNightCalls() {
  601. $result = '';
  602. $nightMode = array();
  603. $minNumLen = 5;
  604. //date range setup
  605. $dateFrom = curmonth() . '-01';
  606. $dateTo = curdate();
  607. //working time setup
  608. $rawWorkTime = $this->altCfg['WORKING_HOURS'];
  609. $rawWorkTime = explode('-', $rawWorkTime);
  610. $workStartTime = $rawWorkTime[0];
  611. $workEndTime = $rawWorkTime[1];
  612. //filling WDYC recalled cache
  613. $recalledCache = array();
  614. if ($this->altCfg['WDYC_ENABLED']) {
  615. if (file_exists(WhyDoYouCall::CACHE_RECALLED)) {
  616. $recalledCache = file_get_contents(WhyDoYouCall::CACHE_RECALLED);
  617. $recalledCache = unserialize($recalledCache);
  618. }
  619. }
  620. $rawCdr = $this->getCDR($dateFrom, $dateTo);
  621. if ($rawCdr !== false) {
  622. $flows = array();
  623. if (!empty($rawCdr)) {
  624. $flows = $this->groupCDRflows($rawCdr);
  625. $flowCounter = 0;
  626. if (!empty($flows)) {
  627. foreach ($flows as $eachFlowId => $eachFlowData) {
  628. $flowCounter++;
  629. $callData = $this->parseCDRFlow($eachFlowData);
  630. $number = $callData['from'];
  631. $date = date("Y-m-d", strtotime($callData['callstart']));
  632. $cutTime = date("H:i:s", strtotime($callData['callstart']));
  633. //night calls preprocessing
  634. if (!zb_isTimeBetween($workStartTime, $workEndTime, $cutTime)) {
  635. if (strlen((string) $number) >= $minNumLen) {
  636. $nightMode[$date][$number][] = $cutTime;
  637. }
  638. }
  639. }
  640. //render data
  641. if (!empty($nightMode)) {
  642. krsort($nightMode);
  643. $allAddress = zb_AddressGetFulladdresslistCached();
  644. $telepathy = new Telepathy(false, true, false, true);
  645. $telepathy->usePhones();
  646. $cells = wf_TableCell(__('Date'));
  647. $cells .= wf_TableCell(__('Phones'));
  648. $rows = wf_TableRow($cells, 'row1');
  649. foreach ($nightMode as $date => $numbers) {
  650. $cells = wf_TableCell($date);
  651. $nums = '';
  652. $times = '';
  653. $guessedLogin = '';
  654. $ncells = wf_TableCell(__('Number'), '30%');
  655. $ncells .= wf_TableCell(__('User'), '30%');
  656. $ncells .= wf_TableCell(__('Time'), '30%');
  657. $nrows = wf_TableRow($ncells, 'row1');
  658. $nums .= wf_TableBody($nrows, '100%', 0);
  659. foreach ($numbers as $eachNumber => $eachTime) {
  660. $guessedLogin = $telepathy->getByPhoneFast($eachNumber, true, true);
  661. if ($guessedLogin) {
  662. $userLabel = wf_link(UserProfile::URL_PROFILE . $guessedLogin, web_profile_icon() . ' ' . @$allAddress[$guessedLogin]);
  663. } else {
  664. $userLabel = '';
  665. }
  666. $eachTime = implode(', ', $eachTime);
  667. $ncells = wf_TableCell($eachNumber, '30%');
  668. $ncells .= wf_TableCell($userLabel, '30%');
  669. $ncells .= wf_TableCell($eachTime, '30%');
  670. if (isset($recalledCache[$eachNumber])) {
  671. $numClass = 'todaysig';
  672. } else {
  673. $numClass = 'row5';
  674. }
  675. $nrows = wf_TableRow($ncells, $numClass);
  676. $nums .= wf_TableBody($nrows, '100%', 0);
  677. }
  678. $cells .= wf_TableCell($nums);
  679. $rows .= wf_TableRow($cells, 'row3');
  680. }
  681. $result .= wf_TableBody($rows, '100%', 0, '');
  682. } else {
  683. $result .= $this->messages->getStyledMessage(__('Nothing to show'), 'info');
  684. }
  685. }
  686. } else {
  687. $result .= $this->messages->getStyledMessage(__('Nothing to show'), 'warning');
  688. }
  689. } else {
  690. $result .= $this->messages->getStyledMessage(__('Strange exception') . ' EX_CDR_GET_FAIL', 'error');
  691. }
  692. return($result);
  693. }
  694. }