api.wdyc.php 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976
  1. <?php
  2. /**
  3. * Missed calls notification subsystem
  4. */
  5. class WhyDoYouCall {
  6. /**
  7. * System alter.ini config as key=>value
  8. *
  9. * @var array
  10. */
  11. protected $altCfg = array();
  12. /**
  13. * Telepathy object placeholder
  14. *
  15. * @var object
  16. */
  17. protected $telepathy = array();
  18. /**
  19. * Contains only mobile flag mapped from WDYC_ONLY_MOBILE config option
  20. *
  21. * @var bool
  22. */
  23. protected $onlyMobileFlag = false;
  24. /**
  25. * Askozia PBX web-interface URL
  26. *
  27. * @var string
  28. */
  29. protected $askoziaUrl = '';
  30. /**
  31. * Askozia PBX administrators login
  32. *
  33. * @var string
  34. */
  35. protected $askoziaLogin = '';
  36. /**
  37. * Askozia PBX administrators password
  38. *
  39. * @var string
  40. */
  41. protected $askoziaPassword = '';
  42. /**
  43. * System messages helper object placeholder
  44. *
  45. * @var object
  46. */
  47. protected $messages = '';
  48. /**
  49. * Contains array of all available user names as login=>reanlnames
  50. *
  51. * @var array
  52. */
  53. protected $allUserNames = array();
  54. /**
  55. * Contains array of all available users address as login=>fulladdress
  56. *
  57. * @var array
  58. */
  59. protected $allUserAddress = array();
  60. /**
  61. * Stats database abstraction layer placeholder
  62. *
  63. * @var object
  64. */
  65. protected $statsDb = '';
  66. /**
  67. * Contains path to the unanswered calls cache
  68. */
  69. const CACHE_FILE = 'exports/whydoyoucall.dat';
  70. /**
  71. * Contains path to recalled phone numbers cache
  72. */
  73. const CACHE_RECALLED = 'exports/whydoyourecall.dat';
  74. /**
  75. * Contains user profile base URL
  76. */
  77. const URL_PROFILE = '?module=userprofile&username=';
  78. /**
  79. * Contains primary module URL
  80. */
  81. const URL_ME = '?module=whydoyoucall';
  82. /**
  83. * Default wdyc stats table name
  84. */
  85. const TABLE_STATS = 'wdycinfo';
  86. public function __construct() {
  87. $this->loadConfig();
  88. $this->initMessages();
  89. $this->initTelepathy();
  90. $this->initStatsDb();
  91. }
  92. /**
  93. * Preloads alter config, for further usage as key=>value
  94. *
  95. * @global object $ubillingConfig
  96. *
  97. * @return void
  98. */
  99. protected function loadConfig() {
  100. global $ubillingConfig;
  101. $this->altCfg = $ubillingConfig->getAlter();
  102. if ($this->altCfg['ASKOZIA_ENABLED']) {
  103. $this->askoziaUrl = zb_StorageGet('ASKOZIAPBX_URL');
  104. $this->askoziaLogin = zb_StorageGet('ASKOZIAPBX_LOGIN');
  105. $this->askoziaPassword = zb_StorageGet('ASKOZIAPBX_PASSWORD');
  106. }
  107. if ((!isset($this->altCfg['WDYC_ONLY_MOBILE'])) OR ( !@$this->altCfg['WDYC_ONLY_MOBILE'])) {
  108. $this->onlyMobileFlag = false;
  109. } else {
  110. $this->onlyMobileFlag = true;
  111. }
  112. }
  113. /**
  114. * Creates message helper object for further usage
  115. *
  116. * @return void
  117. */
  118. protected function initMessages() {
  119. $this->messages = new UbillingMessageHelper();
  120. }
  121. /**
  122. * Inits telepathy object instance
  123. *
  124. * @return void
  125. */
  126. protected function initTelepathy() {
  127. $this->telepathy = new Telepathy();
  128. $this->telepathy->usePhones();
  129. }
  130. /**
  131. * Inits stats database abstraction layer
  132. *
  133. * @return void
  134. */
  135. protected function initStatsDb() {
  136. $this->statsDb = new NyanORM(self::TABLE_STATS);
  137. }
  138. /**
  139. * Askozia PBX data fetching and processing
  140. *
  141. * @return array
  142. */
  143. public function fetchAskoziaCalls() {
  144. $result = array(
  145. 'unanswered' => array(),
  146. 'recalled' => array()
  147. );
  148. $unansweredCalls = array();
  149. $recalledCalls = array();
  150. $missedTries = array();
  151. if ((!empty($this->askoziaUrl)) AND ( !empty($this->askoziaLogin)) AND ( !empty($this->askoziaPassword))) {
  152. $callsTmp = array();
  153. $normalCalls = array();
  154. $incomeTimes = array();
  155. $fields = array(
  156. 'extension_number' => 'all',
  157. 'cdr_filter' => 'incomingoutgoing',
  158. 'period_from' => curdate(),
  159. 'period_to' => curdate(),
  160. 'date_format' => 'Y-m-d',
  161. 'time_format' => 'H:i:s',
  162. 'page_format' => 'A4',
  163. 'SubmitCSVCDR' => 'Download CSV');
  164. $ch = curl_init();
  165. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  166. curl_setopt($ch, CURLOPT_URL, $this->askoziaUrl . '/status_cdr.php');
  167. curl_setopt($ch, CURLOPT_USERPWD, $this->askoziaLogin . ":" . $this->askoziaPassword);
  168. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
  169. curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
  170. $rawResult = curl_exec($ch);
  171. curl_close($ch);
  172. if (!empty($rawResult)) {
  173. $callsTmp = explodeRows($rawResult);
  174. if (!empty($callsTmp)) {
  175. foreach ($callsTmp as $eachline) {
  176. $explode = explode(';', $eachline); //in 2.2.8 delimiter changed from ," to ;
  177. if (!empty($eachline)) {
  178. $normalCalls[] = str_replace('"', '', $explode);
  179. }
  180. }
  181. }
  182. if (!empty($normalCalls)) {
  183. unset($normalCalls[0]);
  184. foreach ($normalCalls as $io => $each) {
  185. //Askozia CFE fix
  186. if (sizeof($each) > 25) {
  187. array_splice($each, 3, 1);
  188. }
  189. $startTime = explode(' ', $each[9]);
  190. @$startTime = $startTime[1];
  191. $incomingNumber = $each[1];
  192. $destinationNumber = $each[2];
  193. $incomeTimes[$incomingNumber] = $each[9];
  194. //calls with less then 24 hours duration
  195. if ($each['13'] < 86400) {
  196. //not answered call
  197. if (ispos($each[14], 'NO ANSWER') OR ( ispos($each[7], 'VoiceMail'))) {
  198. if (!ispos($each[16], 'out')) {
  199. //excluding internal numbers
  200. if (strlen((string) $incomingNumber) > 3) {
  201. $unansweredCalls[$incomingNumber] = $each;
  202. //unanswered calls count
  203. if (isset($missedTries[$incomingNumber])) {
  204. $missedTries[$incomingNumber] ++;
  205. } else {
  206. $missedTries[$incomingNumber] = 1;
  207. }
  208. }
  209. }
  210. } else {
  211. //call was answered after this
  212. if (isset($unansweredCalls[$incomingNumber])) {
  213. unset($unansweredCalls[$incomingNumber]);
  214. } else {
  215. //some country code issues fix
  216. if (ispos($incomingNumber, '380')) {
  217. $uglyIncoming = str_replace('38', '', $incomingNumber);
  218. if (isset($unansweredCalls[$uglyIncoming])) {
  219. unset($unansweredCalls[$uglyIncoming]);
  220. }
  221. }
  222. }
  223. }
  224. //outcoming answered calls
  225. if (($each[2] == $incomingNumber) AND ( ispos($each[14], 'ANSWERED'))) {
  226. if (isset($unansweredCalls[$incomingNumber])) {
  227. unset($unansweredCalls[$incomingNumber]);
  228. }
  229. }
  230. //Unknown numbers not require recall
  231. if (ispos($incomingNumber, 'Unknown')) {
  232. unset($unansweredCalls[$incomingNumber]);
  233. }
  234. //outcoming call success - deleting form unanswered, adding it to recalled cache
  235. if (ispos($each[16], 'out')) {
  236. $reactionTime = 0;
  237. if (ispos($each[14], 'ANSWERED')) {
  238. if ((isset($unansweredCalls[$destinationNumber]))) {
  239. unset($unansweredCalls[$destinationNumber]);
  240. if (isset($recalledCalls[$destinationNumber])) {
  241. $recalledCalls[$destinationNumber]['time'] += $each[13];
  242. $recalledCalls[$destinationNumber]['count'] ++;
  243. } else {
  244. $recalledCalls[$destinationNumber]['time'] = $each[13];
  245. $recalledCalls[$destinationNumber]['count'] = 1;
  246. if (isset($incomeTimes[$destinationNumber])) {
  247. $reactionTime = strtotime($each[9]) - strtotime($incomeTimes[$destinationNumber]);
  248. }
  249. $recalledCalls[$destinationNumber]['trytime'] = $reactionTime;
  250. }
  251. }
  252. $uglyHack = '38' . $destinationNumber; //lol
  253. if (isset($unansweredCalls[$uglyHack])) {
  254. unset($unansweredCalls[$uglyHack]);
  255. if (isset($recalledCalls[$uglyHack])) {
  256. $recalledCalls[$uglyHack]['time'] += $each[13];
  257. $recalledCalls[$uglyHack]['count'] ++;
  258. } else {
  259. $recalledCalls[$uglyHack]['time'] = $each[13];
  260. $recalledCalls[$uglyHack]['count'] = 1;
  261. if (isset($incomeTimes[$uglyHack])) {
  262. $reactionTime = strtotime($each[9]) - strtotime($incomeTimes[$uglyHack]);
  263. }
  264. $recalledCalls[$uglyHack]['trytime'] = $reactionTime;
  265. }
  266. }
  267. } else {
  268. //unsuccessful recall try
  269. if ((isset($unansweredCalls[$destinationNumber]))) {
  270. if (isset($recalledCalls[$destinationNumber])) {
  271. $recalledCalls[$destinationNumber]['time'] += $each[13];
  272. $recalledCalls[$destinationNumber]['count'] ++;
  273. } else {
  274. $recalledCalls[$destinationNumber]['time'] = $each[13];
  275. $recalledCalls[$destinationNumber]['count'] = 1;
  276. if (isset($incomeTimes[$destinationNumber])) {
  277. $reactionTime = strtotime($each[9]) - strtotime($incomeTimes[$destinationNumber]);
  278. }
  279. $recalledCalls[$destinationNumber]['trytime'] = $reactionTime;
  280. }
  281. }
  282. $uglyHack = '38' . $destinationNumber;
  283. if (isset($unansweredCalls[$uglyHack])) {
  284. if (isset($recalledCalls[$uglyHack])) {
  285. $recalledCalls[$uglyHack]['time'] += $each[13];
  286. $recalledCalls[$uglyHack]['count'] ++;
  287. } else {
  288. $recalledCalls[$uglyHack]['time'] = $each[13];
  289. $recalledCalls[$uglyHack]['count'] = 1;
  290. if (isset($incomeTimes[$destinationNumber])) {
  291. $reactionTime = strtotime($each[9]) - strtotime($incomeTimes[$uglyHack]);
  292. }
  293. $recalledCalls[$uglyHack]['trytime'] = $reactionTime;
  294. }
  295. }
  296. }
  297. }
  298. }
  299. }
  300. }
  301. }
  302. }
  303. //appending trys to final result
  304. if (!empty($missedTries)) {
  305. foreach ($missedTries as $missedNumber => $missCount) {
  306. if (isset($unansweredCalls[$missedNumber])) {
  307. $unansweredCalls[$missedNumber]['misscount'] = $missCount;
  308. }
  309. }
  310. }
  311. $result['unanswered'] = $unansweredCalls;
  312. $result['recalled'] = $recalledCalls;
  313. return($result);
  314. }
  315. /**
  316. * Fetches unanswered calls data from Askozia and stored it into cache
  317. *
  318. * @return void
  319. */
  320. public function pollUnansweredCalls() {
  321. $unansweredCalls = array();
  322. $recalledCalls = array();
  323. if ($this->altCfg['ASKOZIA_ENABLED']) {
  324. $fetchedData = $this->fetchAskoziaCalls();
  325. $unansweredCalls = $fetchedData['unanswered'];
  326. $recalledCalls = $fetchedData['recalled'];
  327. print('ASKOZIA:FETCHED' . PHP_EOL);
  328. }
  329. if ($this->altCfg['TELEPONY_ENABLED']) {
  330. if ($this->altCfg['TELEPONY_CDR']) {
  331. $telePony = new TelePony();
  332. $fetchedData = $telePony->fetchMissedCalls();
  333. $unansweredCalls = $fetchedData['unanswered'];
  334. $recalledCalls = $fetchedData['recalled'];
  335. print('TELEPONY:FETCHED' . PHP_EOL);
  336. }
  337. }
  338. //filling recalled calls cache
  339. file_put_contents(self::CACHE_RECALLED, serialize($recalledCalls));
  340. //storing missed calls
  341. file_put_contents(self::CACHE_FILE, serialize($unansweredCalls));
  342. }
  343. /**
  344. * Trys to detect user login by phone number
  345. *
  346. * @param string $phoneNumber
  347. *
  348. * @return string
  349. */
  350. protected function userLoginTelepathy($phoneNumber) {
  351. $result = $this->telepathy->getByPhone($phoneNumber, $this->onlyMobileFlag, $this->onlyMobileFlag); //here only mobile flag is used for number normalization
  352. return ($result);
  353. }
  354. /**
  355. * Renders module controls
  356. *
  357. * @return string
  358. */
  359. public function panel() {
  360. $result = '';
  361. if (!ubRouting::checkGet('renderstats') AND ! ubRouting::checkGet('nightmode')) {
  362. $result .= wf_Link(self::URL_ME, wf_img_sized('skins/icon_phone.gif', '', '16', '16') . ' ' . __('Calls'), false, 'ubButton') . ' ';
  363. if ($this->altCfg['ASKOZIA_ENABLED'] OR $this->altCfg['TELEPONY_CDR']) {
  364. $result .= wf_Link(self::URL_ME . '&nightmode=true', wf_img_sized('skins/icon_moon.png', '', '16', '16') . ' ' . __('Calls during non-business hours'), false, 'ubButton') . ' ';
  365. }
  366. $result .= wf_Link(self::URL_ME . '&renderstats=true', wf_img_sized('skins/icon_stats.gif', '', '16', '16') . ' ' . __('Stats'), false, 'ubButton');
  367. } else {
  368. $result .= wf_BackLink(self::URL_ME);
  369. }
  370. return ($result);
  371. }
  372. /**
  373. * Renders report of missed calls that required to be processed
  374. *
  375. * @return string
  376. */
  377. public function renderMissedCallsReport() {
  378. $result = '';
  379. $this->allUserNames = zb_UserGetAllRealnames();
  380. $this->allUserAddress = zb_AddressGetFulladdresslistCached();
  381. if (file_exists(self::CACHE_FILE)) {
  382. $rawData = file_get_contents(self::CACHE_FILE);
  383. if (!empty($rawData)) {
  384. $rawData = unserialize($rawData);
  385. if (!empty($rawData)) {
  386. $totalCount = 0;
  387. $cells = wf_TableCell(__('Number'));
  388. $cells .= wf_TableCell(__('Last call time'));
  389. $cells .= wf_TableCell(__('Number of attempts to call'));
  390. $cells .= wf_TableCell(__('User'));
  391. $rows = wf_TableRow($cells, 'row1');
  392. foreach ($rawData as $number => $callData) {
  393. $loginDetect = $this->userLoginTelepathy($number);
  394. if (!empty($loginDetect)) {
  395. $userAddress = @$this->allUserAddress[$loginDetect];
  396. $userRealName = @$this->allUserNames[$loginDetect];
  397. $profileLink = wf_Link(self::URL_PROFILE . $loginDetect, web_profile_icon() . ' ' . $userAddress, false) . ' ' . $userRealName;
  398. } else {
  399. $profileLink = '';
  400. }
  401. $cells = wf_TableCell(wf_tag('strong') . $number . wf_tag('strong', true));
  402. $cells .= wf_TableCell($callData[9]);
  403. $cells .= wf_TableCell($callData['misscount']);
  404. $cells .= wf_TableCell($profileLink);
  405. $rows .= wf_TableRow($cells, 'row5');
  406. $totalCount++;
  407. }
  408. $result = wf_TableBody($rows, '100%', 0, 'sortable');
  409. $result .= __('Total') . ': ' . $totalCount;
  410. } else {
  411. $result = $this->messages->getStyledMessage(__('No missed calls at this time'), 'success');
  412. }
  413. }
  414. } else {
  415. $result = $this->messages->getStyledMessage(__('No unanswered calls cache available'), 'warning');
  416. }
  417. return ($result);
  418. }
  419. /**
  420. * Returns report of recalled numbers
  421. *
  422. * @return string
  423. */
  424. public function renderRecalledCallsReport() {
  425. $result = '';
  426. if (file_exists(self::CACHE_RECALLED)) {
  427. $rawData = file_get_contents(self::CACHE_RECALLED);
  428. if (!empty($rawData)) {
  429. $rawData = unserialize($rawData);
  430. if (!empty($rawData)) {
  431. $totalCount = 0;
  432. $cells = wf_TableCell(__('Number'));
  433. $cells .= wf_TableCell(__('Number of attempts to call'));
  434. $cells .= wf_TableCell(__('Reaction time'));
  435. $cells .= wf_TableCell(__('Talk time'));
  436. $cells .= wf_TableCell(__('Status'));
  437. $cells .= wf_TableCell(__('User'));
  438. $rows = wf_TableRow($cells, 'row1');
  439. foreach ($rawData as $number => $callData) {
  440. $callTime = $callData['time'];
  441. $callTimeFormated = zb_formatTime($callTime);
  442. $loginDetect = $this->userLoginTelepathy($number);
  443. if (!empty($loginDetect)) {
  444. $userAddress = @$this->allUserAddress[$loginDetect];
  445. $userRealName = @$this->allUserNames[$loginDetect];
  446. $profileLink = wf_Link(self::URL_PROFILE . $loginDetect, web_profile_icon() . ' ' . $userAddress, false) . ' ' . $userRealName;
  447. } else {
  448. $profileLink = '';
  449. }
  450. $callStatus = ($callTime > 0) ? wf_img('skins/calls/phone_green.png') . ' ' . __('Answered') : wf_img('skins/calls/phone_red.png') . ' ' . __('No answer');
  451. $callStatusFlag = ($callTime > 0) ? 1 : 0;
  452. $cells = wf_TableCell(wf_tag('strong') . $number . wf_tag('strong', true));
  453. $cells .= wf_TableCell($callData['count']);
  454. $cells .= wf_TableCell(zb_formatTime($callData['trytime']));
  455. $cells .= wf_TableCell($callTimeFormated, '', '', 'sorttable_customkey="' . $callTime . '"');
  456. $cells .= wf_TableCell($callStatus, '', '', 'sorttable_customkey="' . $callStatusFlag . '"');
  457. $cells .= wf_TableCell($profileLink);
  458. $rows .= wf_TableRow($cells, 'row5');
  459. $totalCount++;
  460. }
  461. $result = wf_TableBody($rows, '100%', 0, 'sortable');
  462. $result .= __('Total') . ': ' . $totalCount;
  463. } else {
  464. $result = $this->messages->getStyledMessage(__('No recalled calls at this time'), 'info');
  465. }
  466. }
  467. } else {
  468. $result = $this->messages->getStyledMessage(__('No recalled calls cache available'), 'warning');
  469. }
  470. return ($result);
  471. }
  472. /**
  473. * Saves day unansweres/recalls stats into database
  474. *
  475. * @return void
  476. */
  477. public function saveStats() {
  478. $date = curdate();
  479. $missedCallsCount = 0;
  480. $recallsCount = 0;
  481. $unsuccCount = 0;
  482. $totalTryTime = 0;
  483. $missedCallsNumbers = '';
  484. //missed calls stats
  485. if (file_exists(self::CACHE_FILE)) {
  486. $rawData = file_get_contents(self::CACHE_FILE);
  487. if (!empty($rawData)) {
  488. $rawData = unserialize($rawData);
  489. if (!empty($rawData)) {
  490. foreach ($rawData as $missedNumber => $callData) {
  491. if (!ispos($missedNumber, 'anonymous')) {
  492. $missedCallsNumbers .= $missedNumber . ' ';
  493. $missedCallsCount++;
  494. }
  495. }
  496. }
  497. }
  498. }
  499. //recalled calls stats
  500. if (file_exists(self::CACHE_RECALLED)) {
  501. $rawData = file_get_contents(self::CACHE_RECALLED);
  502. if (!empty($rawData)) {
  503. $rawData = unserialize($rawData);
  504. if (!empty($rawData)) {
  505. $recallsCount = sizeof($rawData);
  506. foreach ($rawData as $recalledNumber => $callData) {
  507. if ($callData['time'] == 0) {
  508. $unsuccCount++;
  509. }
  510. if (isset($callData['trytime'])) {
  511. //realistic?
  512. if ($callData['trytime'] > 0) {
  513. $trytime = $callData['trytime'];
  514. } else {
  515. //negative?
  516. $trytime = 0;
  517. //seems this case is better than abs() value to prevent trytime exhaustion
  518. }
  519. $totalTryTime = $totalTryTime + $trytime;
  520. }
  521. }
  522. }
  523. }
  524. }
  525. $missedCallsNumbers = mysql_real_escape_string($missedCallsNumbers);
  526. $this->statsDb->data('date', $date);
  527. $this->statsDb->data('missedcount', $missedCallsCount);
  528. $this->statsDb->data('recallscount', $recallsCount);
  529. $this->statsDb->data('unsucccount', $unsuccCount);
  530. $this->statsDb->data('missednumbers', $missedCallsNumbers);
  531. $this->statsDb->data('totaltrytime', $totalTryTime);
  532. $this->statsDb->create();
  533. }
  534. /**
  535. * Returns date search form
  536. *
  537. * @param int $year
  538. * @param int $month
  539. *
  540. * @return string
  541. */
  542. protected function statsDateForm($year = '', $month = '') {
  543. $result = '';
  544. $curYear = (empty($year)) ? date("Y") : $year;
  545. $curMonth = (empty($month)) ? date("m") : $month;
  546. $monthArr = months_array_localized();
  547. $inputs = wf_YearSelectorPreset('yearsel', __('Year'), false, $curYear) . ' ';
  548. $inputs .= wf_Selector('monthsel', $monthArr, __('Month'), $curMonth, false) . ' ';
  549. $inputs .= wf_Submit(__('Show'));
  550. $result .= wf_Form('', 'POST', $inputs, 'glamour');
  551. $result .= wf_CleanDiv();
  552. return ($result);
  553. }
  554. /**
  555. * Renders unanswered night-mode calls
  556. *
  557. * @return string
  558. */
  559. public function renderNightModeCalls() {
  560. $result = '';
  561. if ($this->altCfg['ASKOZIA_ENABLED']) {
  562. $result .= $this->getAskoziaNightModeCalls();
  563. }
  564. if ($this->altCfg['TELEPONY_ENABLED'] AND $this->altCfg['TELEPONY_CDR']) {
  565. $telePony = new TelePony();
  566. $result .= $telePony->renderNightCalls();
  567. }
  568. return($result);
  569. }
  570. /**
  571. * Fetches unanswered night-mode calls from Askozia
  572. *
  573. * @return string
  574. */
  575. public function getAskoziaNightModeCalls() {
  576. $result = '';
  577. $askoziaUrl = zb_StorageGet('ASKOZIAPBX_URL');
  578. $askoziaLogin = zb_StorageGet('ASKOZIAPBX_LOGIN');
  579. $askoziaPassword = zb_StorageGet('ASKOZIAPBX_PASSWORD');
  580. $cacheTime = 3600;
  581. $recalledCache = array();
  582. if (file_exists(self::CACHE_RECALLED)) {
  583. $recalledCache = file_get_contents(self::CACHE_RECALLED);
  584. $recalledCache = unserialize($recalledCache);
  585. }
  586. $showYear = (ubRouting::checkPost('showyear')) ? ubRouting::post('showyear', 'int') : curyear();
  587. $showMonth = (ubRouting::checkPost('showmonth')) ? ubRouting::post('showmonth', 'int') : date('m');
  588. $cache = new UbillingCache();
  589. $cacheKey = 'ASKOZIA_NIGHTMODE_' . $showYear . $showMonth;
  590. $from = $showYear . '-' . $showMonth . '-01';
  591. $to = date("Y-m-t", strtotime($from));
  592. $result = '';
  593. $rawResult = $cache->get($cacheKey, $cacheTime);
  594. if (empty($rawResult)) {
  595. $sip = new OmaeUrl($askoziaUrl . '/status_cdr.php');
  596. $sip->dataPost('extension_number', 'all');
  597. $sip->dataPost('cdr_filter', 'incomingoutgoing');
  598. $sip->dataPost('period_from', $from);
  599. $sip->dataPost('period_to', $to);
  600. $sip->dataPost('date_format', 'Y-m-d');
  601. $sip->dataPost('time_format', 'H:i:s');
  602. $sip->dataPost('page_format', 'A4');
  603. $sip->dataPost('SubmitCSVCDR', 'Download CSV');
  604. $sip->setBasicAuth($askoziaLogin, $askoziaPassword);
  605. $rawResult = $sip->response();
  606. $cache->set($cacheKey, $rawResult, $cacheTime);
  607. }
  608. if (!empty($rawResult)) {
  609. $normalData = array();
  610. $callersData = array();
  611. $nightMode = array();
  612. $data = explodeRows($rawResult);
  613. $data = array_reverse($data);
  614. if (!empty($data)) {
  615. foreach ($data as $eachline) {
  616. $explode = explode(';', $eachline); //in 2.2.8 delimiter changed from ," to ;
  617. if (!empty($eachline)) {
  618. $normalData[] = str_replace('"', '', $explode);
  619. }
  620. }
  621. }
  622. //filters form here
  623. $playBackFlag = false;
  624. $inputs = '';
  625. $inputs .= wf_YearSelectorPreset('showyear', __('Year'), false, $showYear) . ' ';
  626. $inputs .= wf_MonthSelector('showmonth', __('Month'), $showMonth, false) . ' ';
  627. $inputs .= wf_Submit(__('Show'));
  628. $result .= wf_Form('', 'POST', $inputs, 'glamour');
  629. $result .= wf_delimiter();
  630. if (!empty($normalData)) {
  631. foreach ($normalData as $io => $each) {
  632. $number = $each[1];
  633. $dateTime = $each[10];
  634. $date = date("Y-m-d", strtotime($dateTime));
  635. $cutTime = date("H:i:s", strtotime($dateTime));
  636. if ($playBackFlag) {
  637. if ($each[3] == 'nightswitch-application' AND $each[7] == 'Playback') {
  638. $nightMode[$date][$number][] = $cutTime;
  639. }
  640. } else {
  641. if ($each[3] == 'nightswitch-application') {
  642. $nightMode[$date][$number][] = $cutTime;
  643. }
  644. }
  645. }
  646. }
  647. if (!empty($nightMode)) {
  648. $allAddress = zb_AddressGetFulladdresslistCached();
  649. $telepathy = new Telepathy(false, true, false, true);
  650. $telepathy->usePhones();
  651. $cells = wf_TableCell(__('Date'));
  652. $cells .= wf_TableCell(__('Phones'));
  653. $rows = wf_TableRow($cells, 'row1');
  654. foreach ($nightMode as $date => $numbers) {
  655. $cells = wf_TableCell($date);
  656. $nums = '';
  657. $times = '';
  658. $guessedLogin = '';
  659. $ncells = wf_TableCell(__('Number'), '30%');
  660. $ncells .= wf_TableCell(__('User'), '30%');
  661. $ncells .= wf_TableCell(__('Time'), '30%');
  662. $nrows = wf_TableRow($ncells, 'row1');
  663. $nums .= wf_TableBody($nrows, '100%', 0);
  664. foreach ($numbers as $eachNumber => $eachTime) {
  665. $guessedLogin = $telepathy->getByPhoneFast($eachNumber, true, true);
  666. if ($guessedLogin) {
  667. $userLabel = wf_link(UserProfile::URL_PROFILE . $guessedLogin, web_profile_icon() . ' ' . @$allAddress[$guessedLogin]);
  668. } else {
  669. $userLabel = '';
  670. }
  671. $eachTime = implode(', ', $eachTime);
  672. $ncells = wf_TableCell($eachNumber, '30%');
  673. $ncells .= wf_TableCell($userLabel, '30%');
  674. $ncells .= wf_TableCell($eachTime, '30%');
  675. if (isset($recalledCache[$eachNumber])) {
  676. $numClass = 'todaysig';
  677. } else {
  678. $numClass = 'row5';
  679. }
  680. $nrows = wf_TableRow($ncells, $numClass);
  681. $nums .= wf_TableBody($nrows, '100%', 0);
  682. }
  683. $cells .= wf_TableCell($nums);
  684. $rows .= wf_TableRow($cells, 'row3');
  685. }
  686. $result .= wf_TableBody($rows, '100%', 0, '');
  687. } else {
  688. $messages = new UbillingMessageHelper();
  689. $result .= $messages->getStyledMessage(__('Nothing to show'), 'info');
  690. }
  691. } else {
  692. show_warning(__('Something went wrong') . ' ' . __('Nothing found'));
  693. }
  694. return($result);
  695. }
  696. /**
  697. * Renders previous days stats
  698. *
  699. * @return string
  700. */
  701. public function renderStats() {
  702. $result = '';
  703. $year = (wf_CheckPost(array('yearsel'))) ? vf($_POST['yearsel'], 3) : date("Y");
  704. $month = (wf_CheckPost(array('monthsel'))) ? vf($_POST['monthsel'], 3) : date("m");
  705. $totalMissed = 0;
  706. $totalRecalls = 0;
  707. $totalUnsucc = 0;
  708. $totalCalls = 0;
  709. $totalReactTime = 0;
  710. $result .= $this->statsDateForm($year, $month);
  711. $gchartsData = array();
  712. $gchartsData[] = array(__('Date'), __('Missed calls'), __('Recalled calls'), __('Unsuccessful recalls'));
  713. $chartsOptions = "
  714. 'focusTarget': 'category',
  715. 'hAxis': {
  716. 'color': 'none',
  717. 'baselineColor': 'none',
  718. },
  719. 'vAxis': {
  720. 'color': 'none',
  721. 'baselineColor': 'none',
  722. },
  723. 'curveType': 'function',
  724. 'pointSize': 5,
  725. 'crosshair': {
  726. trigger: 'none'
  727. },";
  728. $jqDtOpts = '"order": [[ 0, "desc" ]]';
  729. $columns = array('ID', 'Date', 'Missed calls', 'Recalled calls', 'Unsuccessful recalls', 'Reaction time', 'Phones');
  730. if (cfr('ROOT')) {
  731. $columns[] = 'Actions';
  732. }
  733. $this->statsDb->where('date', 'LIKE', $year . "-" . $month . "-%");
  734. $all = $this->statsDb->getAll();
  735. if (!empty($all)) {
  736. foreach ($all as $io => $each) {
  737. $gchartsData[] = array($each['date'], $each['missedcount'], $each['recallscount'], $each['unsucccount']);
  738. $totalMissed += $each['missedcount'];
  739. $totalRecalls += $each['recallscount'];
  740. $totalUnsucc += $each['unsucccount'];
  741. $totalReactTime += $each['totaltrytime'];
  742. }
  743. $totalCalls += $totalMissed + $totalRecalls;
  744. $result .= wf_gchartsLine($gchartsData, __('Calls'), '100%', '300px;', $chartsOptions);
  745. $result .= wf_tag('strong') . __('Total') . ': ' . wf_tag('strong', true) . wf_tag('br');
  746. $result .= __('Missed calls') . ' - ' . $totalMissed . wf_tag('br');
  747. $result .= __('Recalled calls') . ' - ' . $totalRecalls . wf_tag('br');
  748. $result .= __('Unsuccessful recalls') . ' - ' . $totalUnsucc . wf_tag('br');
  749. $result .= __('Percent') . ' ' . __('Missed calls') . ' - ' . zb_PercentValue($totalCalls, $totalMissed) . '%' . wf_tag('br');
  750. $reactTimeStat = (!empty($totalReactTime)) ? zb_formatTime($totalReactTime / ($totalRecalls + $totalUnsucc)) : __('No');
  751. $result .= __('Reaction time') . ' - ' . $reactTimeStat;
  752. $result .= wf_tag('br');
  753. $result .= wf_tag('br');
  754. $result .= wf_JqDtLoader($columns, self::URL_ME . '&renderstats=true&ajaxlist=true&year=' . $year . '&month=' . $month, false, __('Calls'), 50, $jqDtOpts);
  755. } else {
  756. $result .= $this->messages->getStyledMessage(__('Nothing found'), 'warning');
  757. }
  758. return ($result);
  759. }
  760. /**
  761. * Cuts string to some normal length
  762. *
  763. * @param string $string
  764. * @param int $count
  765. * @return string
  766. */
  767. protected function cutString($string, $count) {
  768. if (strlen($string) <= $count) {
  769. $result = $string;
  770. } else {
  771. $result = substr($string, 0, $count) . '...';
  772. }
  773. return ($result);
  774. }
  775. /**
  776. * Do some coloring of missed counts
  777. *
  778. * @param int $missedCount
  779. *
  780. * @return string
  781. */
  782. protected function colorMissed($missedCount) {
  783. if ($missedCount > 0) {
  784. if ($missedCount <= 5) {
  785. $result = $missedCount;
  786. } else {
  787. $result = wf_tag('font', false, '', 'color="#FF0000"') . $missedCount . wf_tag('font', true);
  788. }
  789. } else {
  790. $result = wf_tag('font', false, '', 'color="#118819"') . $missedCount . wf_tag('font', true);
  791. }
  792. return ($result);
  793. }
  794. /**
  795. * Renders stats records editing form
  796. *
  797. * @param array $statsRecordData
  798. *
  799. * @return string
  800. */
  801. protected function renderStatsEditForm($statsRecordData) {
  802. $result = '';
  803. if (!empty($statsRecordData)) {
  804. $inputs = wf_HiddenInput('editwdycstatsid', $statsRecordData['id']);
  805. $inputs .= wf_TextInput('editwdycstatsmissedcount', __('Missed calls'), $statsRecordData['missedcount'], true, 2, 'digits');
  806. $inputs .= wf_TextInput('editwdycstatsrecallscount', __('Recalled calls'), $statsRecordData['recallscount'], true, 2, 'digits');
  807. $inputs .= wf_TextInput('editwdycstatsmissednumbers', __('Phones'), $statsRecordData['missednumbers'], true, 20, '');
  808. $inputs .= wf_Submit(__('Save'));
  809. $result .= wf_Form('', 'POST', $inputs, 'glamour');
  810. }
  811. return($result);
  812. }
  813. /**
  814. * Catches existing stats modification request and performs editing
  815. *
  816. * @return void
  817. */
  818. public function saveEditedStats() {
  819. if (ubRouting::checkPost('editwdycstatsid')) {
  820. $editId = ubRouting::post('editwdycstatsid', 'int');
  821. $newMissCount = ubRouting::post('editwdycstatsmissedcount', 'int');
  822. $newRecallsCount = ubRouting::post('editwdycstatsrecallscount', 'int');
  823. $newPhones = ubRouting::post('editwdycstatsmissednumbers', 'mres');
  824. if (!empty($editId)) {
  825. $this->statsDb->where('id', '=', $editId);
  826. $recordData = $this->statsDb->getAll();
  827. if (!empty($recordData)) {
  828. $recordData = $recordData[0];
  829. $oldMissCount = $recordData['missedcount'];
  830. $oldRecallsCount = $recordData['recallscount'];
  831. $oldPhones = $recordData['missednumbers'];
  832. //is anything changed?
  833. if ($newMissCount != $oldMissCount OR $newRecallsCount != $oldRecallsCount OR $newPhones != $oldPhones) {
  834. $this->statsDb->where('id', '=', $editId);
  835. $this->statsDb->data('missedcount', $newMissCount);
  836. $this->statsDb->data('recallscount', $newRecallsCount);
  837. $this->statsDb->data('missednumbers', $newPhones);
  838. $this->statsDb->save();
  839. log_register('WDYC CHANGED [' . $editId . '] MISSED `' . $oldMissCount . '` ON `' . $newMissCount . '` RECALLS `' . $oldRecallsCount . '` ON `' . $newRecallsCount . '`');
  840. }
  841. }
  842. }
  843. }
  844. }
  845. /**
  846. * Renders json for previous calls stats
  847. *
  848. * @param int $year
  849. * @param int $month
  850. *
  851. * @return void
  852. */
  853. public function jsonPreviousStats($year, $month) {
  854. $json = new wf_JqDtHelper();
  855. $this->statsDb->where('date', 'LIKE', $year . "-" . $month . "-%");
  856. $all = $this->statsDb->getAll();
  857. $data = array();
  858. $actColumnFlag = (cfr('ROOT')) ? true : false;
  859. if (!empty($all)) {
  860. foreach ($all as $io => $each) {
  861. $data[] = $each['id'];
  862. $data[] = $each['date'];
  863. $data[] = $this->colorMissed($each['missedcount']);
  864. $data[] = $each['recallscount'];
  865. $data[] = $each['unsucccount'];
  866. $reactTime = (!empty($each['totaltrytime'])) ? zb_formatTime(($each['totaltrytime'] / ($each['recallscount'] + $each['unsucccount']))) : '-';
  867. $data[] = $reactTime;
  868. $data[] = $this->cutString($each['missednumbers'], 45);
  869. if ($actColumnFlag) {
  870. $data[] = wf_modalAuto(web_edit_icon(), __('Edit') . ' ' . $each['date'], $this->renderStatsEditForm($each));
  871. }
  872. $json->addRow($data);
  873. unset($data);
  874. }
  875. }
  876. $json->getJson();
  877. }
  878. }