Total Complexity | 204 |
Total Lines | 1335 |
Duplicated Lines | 0 % |
Changes | 18 | ||
Bugs | 0 | Features | 0 |
Complex classes like WorkerCallEvents often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use WorkerCallEvents, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
19 | class WorkerCallEvents extends WorkerBase |
||
20 | { |
||
21 | // Максимальное количество экземпляров данныого класса. |
||
22 | protected int $maxProc = 1; |
||
23 | |||
24 | protected array $mixMonitorChannels = []; |
||
25 | protected bool $record_calls = true; |
||
26 | protected bool $split_audio_thread = false; |
||
27 | protected array $checkChanHangupTransfer = []; |
||
28 | |||
29 | /** |
||
30 | * Инициирует запись разговора на канале. |
||
31 | * |
||
32 | * @param string $channel |
||
33 | * @param ?string $file_name |
||
34 | * @param ?string $sub_dir |
||
35 | * @param ?string $full_name |
||
36 | * @param bool $onlySetVar |
||
37 | * |
||
38 | * @return string |
||
39 | */ |
||
40 | public function MixMonitor($channel, $file_name = null, $sub_dir = null, $full_name = null, $onlySetVar = false): string |
||
93 | } |
||
94 | |||
95 | /** |
||
96 | * Останавливает запись разговора на канале. |
||
97 | * @param string $channel |
||
98 | */ |
||
99 | public function StopMixMonitor($channel): void |
||
100 | { |
||
101 | if(isset($this->mixMonitorChannels[$channel])){ |
||
102 | unset($this->mixMonitorChannels[$channel]); |
||
103 | }else{ |
||
104 | return; |
||
105 | } |
||
106 | if ($this->record_calls) { |
||
107 | $res = $this->am->StopMixMonitor($channel); |
||
108 | $res['cmd'] = "StopMixMonitor($channel)"; |
||
109 | CdrDb::LogEvent(json_encode($res)); |
||
110 | } |
||
111 | } |
||
112 | |||
113 | /** |
||
114 | * Обработка события начала телефонного звонка. |
||
115 | * |
||
116 | * @param $data |
||
117 | */ |
||
118 | public function Action_dial($data): void |
||
119 | { |
||
120 | $this->insertDataToDbM($data); |
||
121 | $this->Action_app_end($data); |
||
122 | } |
||
123 | |||
124 | /** |
||
125 | * Завершение работы приложения. |
||
126 | * |
||
127 | * @param $data |
||
128 | */ |
||
129 | public function Action_app_end($data): void |
||
145 | } |
||
146 | } |
||
147 | } |
||
148 | |||
149 | /** |
||
150 | * Обработка события создания канала - пары, при начале телефонного звонка. |
||
151 | * |
||
152 | * @param $data |
||
153 | */ |
||
154 | public function Action_dial_create_chan($data): void |
||
231 | } |
||
232 | } |
||
233 | } |
||
234 | |||
235 | /** |
||
236 | * Обработка события ответа на звонок. Соединение абонентов. |
||
237 | * |
||
238 | * @param $data |
||
239 | */ |
||
240 | public function Action_dial_answer($data): void |
||
357 | } |
||
358 | } |
||
359 | } |
||
360 | } |
||
361 | |||
362 | /** |
||
363 | * Завершение / уничтожение канала. |
||
364 | * |
||
365 | * @param $data |
||
366 | * |
||
367 | * @throws Error | \Exception |
||
368 | */ |
||
369 | public function Action_hangup_chan($data): void |
||
386 | } |
||
387 | } |
||
388 | |||
389 | /** |
||
390 | * Проверяем на SIP трансфер. |
||
391 | * @param $data |
||
392 | * @param $channels |
||
393 | */ |
||
394 | private function hangupChanCheckSipTrtansfer($data, $channels):void{ |
||
395 | $not_local = (stripos($data['agi_channel'], 'local/') === false); |
||
396 | if($not_local === false || $data['OLD_LINKEDID'] !== $data['linkedid']) { |
||
397 | return; |
||
398 | } |
||
399 | $active_chans = $this->am->GetChannels(false); |
||
400 | // Намек на SIP трансфер. |
||
401 | foreach ($channels as $data_chan) { |
||
402 | if ( ! in_array($data_chan['chan'], $active_chans, true)) { |
||
403 | // Вызов уже завершен. Не интересно. |
||
404 | continue; |
||
405 | } |
||
406 | $BRIDGEPEER = $this->am->GetVar($data_chan['chan'], 'BRIDGEPEER', null, false); |
||
407 | if ( ! in_array($BRIDGEPEER, $active_chans, true) || !is_string($BRIDGEPEER)) { |
||
408 | // Вызов уже завершен. Не интересно. |
||
409 | continue; |
||
410 | } |
||
411 | |||
412 | $linkedid = $this->am->GetVar($data_chan['chan'], 'CDR(linkedid)', null, false); |
||
413 | if ( empty($linkedid) || $linkedid === $data['linkedid']) { |
||
414 | continue; |
||
415 | } |
||
416 | |||
417 | $CALLERID = $this->am->GetVar($BRIDGEPEER, 'CALLERID(num)', null, false); |
||
418 | $n_data['action'] = 'sip_transfer'; |
||
419 | $n_data['src_chan'] = $data_chan['out'] ? $data_chan['chan'] : $BRIDGEPEER; |
||
420 | $n_data['src_num'] = $data_chan['out'] ? $data_chan['num'] : $CALLERID; |
||
421 | $n_data['dst_chan'] = $data_chan['out'] ? $BRIDGEPEER : $data_chan['chan']; |
||
422 | $n_data['dst_num'] = $data_chan['out'] ? $CALLERID : $data_chan['num']; |
||
423 | $n_data['start'] = date('Y-m-d H:i:s'); |
||
424 | $n_data['answer'] = date('Y-m-d H:i:s'); |
||
425 | $n_data['linkedid'] = $linkedid; |
||
426 | $n_data['UNIQUEID'] = $data['linkedid'] . Util::generateRandomString(); |
||
427 | $n_data['transfer'] = '0'; |
||
428 | $n_data['recordingfile'] = $this->MixMonitor($n_data['dst_chan'], $n_data['UNIQUEID']); |
||
429 | $n_data['did'] = $data_chan['did']; |
||
430 | |||
431 | Util::logMsgDb('call_events', json_encode($n_data)); |
||
432 | $this->insertDataToDbM($n_data); |
||
433 | $filter = [ |
||
434 | 'linkedid=:linkedid:', |
||
435 | 'bind' => ['linkedid' => $data['linkedid']], |
||
436 | ]; |
||
437 | $m_data = CallDetailRecordsTmp::find($filter); |
||
438 | foreach ($m_data as $row) { |
||
439 | $row->writeAttribute('linkedid', $linkedid); |
||
440 | $row->save(); |
||
441 | } |
||
442 | |||
443 | /** |
||
444 | * Отправка UserEvent |
||
445 | */ |
||
446 | $AgiData = base64_encode(json_encode($n_data)); |
||
447 | $this->am->UserEvent('CdrConnector', ['AgiData' => $AgiData]); |
||
448 | } // Обход текущих каналов. |
||
449 | } |
||
450 | |||
451 | /** |
||
452 | * Обработка события уничтожения канала. |
||
453 | * @param array $data |
||
454 | * @param array $transfer_calls |
||
455 | * @param array $channels |
||
456 | */ |
||
457 | private function hangupChanEndCalls(array $data, array &$transfer_calls, array &$channels):void{ |
||
458 | $filter = [ |
||
459 | 'linkedid=:linkedid: AND endtime = "" AND (src_chan=:src_chan: OR dst_chan=:dst_chan:)', |
||
460 | 'bind' => [ |
||
461 | 'linkedid' => $data['linkedid'], |
||
462 | 'src_chan' => $data['agi_channel'], |
||
463 | 'dst_chan' => $data['agi_channel'], |
||
464 | ], |
||
465 | ]; |
||
466 | /** @var CallDetailRecordsTmp $m_data */ |
||
467 | /** @var CallDetailRecordsTmp $row */ |
||
468 | $m_data = CallDetailRecordsTmp::find($filter); |
||
469 | foreach ($m_data as $row) { |
||
470 | if ($row->transfer == 1) { |
||
471 | $transfer_calls[] = $row->toArray(); |
||
472 | } |
||
473 | if ($row->dialstatus === 'ORIGINATE') { |
||
474 | $row->writeAttribute('dialstatus', ''); |
||
475 | if($row->answer === ''){ |
||
476 | $newId = $row->linkedid.'_'.$row->src_num.'_'.substr($row->src_chan, strpos($row->src_chan,'-') +1); |
||
477 | $row->writeAttribute('UNIQUEID', $newId); |
||
478 | } |
||
479 | } |
||
480 | $row->writeAttribute('endtime', $data['end']); |
||
481 | $row->writeAttribute('transfer', 0); |
||
482 | if ($data['dialstatus'] !== '') { |
||
483 | if ($data['dialstatus'] === 'ORIGINATE') { |
||
484 | $row->writeAttribute('dst_chan', ''); |
||
485 | } |
||
486 | $row->writeAttribute('dialstatus', $data['dialstatus']); |
||
487 | } |
||
488 | $res = $row->update(); |
||
489 | if ( ! $res) { |
||
490 | Util::sysLogMsg('Action_hangup_chan', implode(' ', $row->getMessages())); |
||
491 | } |
||
492 | |||
493 | if ($row->src_chan !== $data['agi_channel']) { |
||
494 | $channels[] = [ |
||
495 | 'chan' => $row->src_chan, |
||
496 | 'did' => $row->did, |
||
497 | 'num' => $row->src_num, |
||
498 | 'out' => true, |
||
499 | ]; |
||
500 | } else { |
||
501 | $this->StopMixMonitor($row->dst_chan); |
||
502 | $channels[] = [ |
||
503 | 'chan' => $row->dst_chan, |
||
504 | 'did' => $row->did, |
||
505 | 'num' => $row->dst_num, |
||
506 | 'out' => false, |
||
507 | ]; |
||
508 | } |
||
509 | } |
||
510 | } |
||
511 | |||
512 | /** |
||
513 | * Логирование истории звонков при трасфере. |
||
514 | * |
||
515 | * @param $action |
||
516 | * @param $data |
||
517 | * @param ?array $calls_data |
||
518 | */ |
||
519 | public function Action_CreateRowTransfer($action, $data, $calls_data = null): void |
||
520 | { |
||
521 | if( isset($this->checkChanHangupTransfer[$data['agi_channel']]) ) { |
||
522 | return; |
||
523 | }else{ |
||
524 | $this->checkChanHangupTransfer[$data['agi_channel']] = $action; |
||
525 | } |
||
526 | |||
527 | if (null === $calls_data) { |
||
528 | $filter = [ |
||
529 | 'linkedid=:linkedid: AND endtime = "" AND transfer=1 AND (src_chan=:chan: OR dst_chan=:chan:)', |
||
530 | 'bind' => [ |
||
531 | 'linkedid' => $data['linkedid'], |
||
532 | 'chan' => $data['agi_channel'], |
||
533 | ], |
||
534 | 'order' => 'is_app', |
||
535 | ]; |
||
536 | $m_data = CallDetailRecordsTmp::find($filter); |
||
537 | $calls_data = $m_data->toArray(); |
||
538 | } |
||
539 | |||
540 | if (count($calls_data) === 2) { |
||
541 | $insert_data = []; |
||
542 | foreach ($calls_data as $row_data) { |
||
543 | if ($row_data['src_chan'] === $data['agi_channel']) { |
||
544 | $fname_chan = isset($insert_data['dst_chan']) ? 'src_chan' : 'dst_chan'; |
||
545 | $fname_num = isset($insert_data['dst_num']) ? 'src_num' : 'dst_num'; |
||
546 | |||
547 | $insert_data[$fname_chan] = $row_data['dst_chan']; |
||
548 | $insert_data[$fname_num] = $row_data['dst_num']; |
||
549 | } else { |
||
550 | $fname_chan = ! isset($insert_data['src_chan']) ? 'src_chan' : 'dst_chan'; |
||
551 | $fname_num = ! isset($insert_data['src_num']) ? 'src_num' : 'dst_num'; |
||
552 | |||
553 | $insert_data[$fname_chan] = $row_data['src_chan']; |
||
554 | $insert_data[$fname_num] = $row_data['src_num']; |
||
555 | } |
||
556 | } |
||
557 | // Запись разговора. |
||
558 | $this->StopMixMonitor($insert_data['src_chan']); |
||
559 | $this->StopMixMonitor($insert_data['dst_chan']); |
||
560 | $recordingfile = $this->MixMonitor( |
||
561 | $insert_data['dst_chan'], |
||
562 | 'transfer_' . $insert_data['src_num'] . '_' . $insert_data['dst_num'] . '_' . $data['linkedid'] |
||
563 | ); |
||
564 | // |
||
565 | $insert_data['action'] = "{$action}_end_trasfer"; |
||
566 | $insert_data['recordingfile'] = $recordingfile; |
||
567 | $insert_data['start'] = $data['end']; |
||
568 | $insert_data['answer'] = $data['end']; |
||
569 | $insert_data['linkedid'] = $data['linkedid']; |
||
570 | $insert_data['UNIQUEID'] = $data['agi_threadid']; |
||
571 | $insert_data['did'] = $data['did']; |
||
572 | $insert_data['transfer'] = '0'; |
||
573 | |||
574 | /** |
||
575 | * Отправка UserEvent |
||
576 | */ |
||
577 | $insert_data['action'] = 'transfer_dial_create_cdr'; |
||
578 | |||
579 | $AgiData = base64_encode(json_encode($insert_data)); |
||
580 | $this->am->UserEvent('CdrConnector', ['AgiData' => $AgiData]); |
||
581 | |||
582 | $this->insertDataToDbM($insert_data); |
||
583 | CdrDb::LogEvent(json_encode($insert_data)); |
||
584 | } elseif (empty($calls_data[0]['answer']) && count($calls_data) === 1 && ! empty($calls_data[0]['recordingfile'])) { |
||
585 | // Возобновление записи разговора при срыве переадресации. |
||
586 | $row_data = $calls_data[0]; |
||
587 | $chan = ($data['agi_channel'] === $row_data['src_chan']) ? $row_data['dst_chan'] : $row_data['src_chan']; |
||
588 | $filter = [ |
||
589 | 'linkedid=:linkedid: AND endtime = ""', |
||
590 | 'bind' => [ |
||
591 | 'linkedid' => $data['linkedid'], |
||
592 | ], |
||
593 | 'order' => 'is_app', |
||
594 | ]; |
||
595 | /** @var CallDetailRecordsTmp $not_ended_cdr */ |
||
596 | $cdr = CallDetailRecordsTmp::find($filter); |
||
597 | /** @var CallDetailRecordsTmp $row */ |
||
598 | $not_ended_cdr = null; |
||
599 | $transferNotComplete = false; |
||
600 | foreach ($cdr as $row){ |
||
601 | if($row->transfer === '1' && ($row->src_chan === $chan || $row->dst_chan === $chan) ){ |
||
602 | $not_ended_cdr = $row; |
||
603 | } |
||
604 | if($row->answer === '' && $row->endtime === '' |
||
605 | && ($row->src_chan === $chan || $row->dst_chan === $chan) ){ |
||
606 | $transferNotComplete = true; |
||
607 | } |
||
608 | } |
||
609 | |||
610 | if ($not_ended_cdr !== null && !$transferNotComplete) { |
||
611 | $this->StopMixMonitor($not_ended_cdr->src_chan); |
||
612 | $this->MixMonitor($not_ended_cdr->dst_chan, '', '', $not_ended_cdr->recordingfile); |
||
613 | } |
||
614 | } |
||
615 | } |
||
616 | |||
617 | /** |
||
618 | * Обработка начала переадресации. |
||
619 | * |
||
620 | * @param $data |
||
621 | */ |
||
622 | public function Action_transfer_dial($data): void |
||
623 | { |
||
624 | $this->Action_transfer_check($data); |
||
625 | $this->insertDataToDbM($data); |
||
626 | $this->Action_app_end($data); |
||
627 | } |
||
628 | |||
629 | /** |
||
630 | * Проверка на транфер |
||
631 | * |
||
632 | * @param $data |
||
633 | */ |
||
634 | public function Action_transfer_check($data): void |
||
635 | { |
||
636 | $filter = [ |
||
637 | 'linkedid=:linkedid: AND endtime = "" AND transfer=0 AND (src_chan=:src_chan: OR dst_chan=:src_chan:)', |
||
638 | 'bind' => [ |
||
639 | 'linkedid' => $data['linkedid'], |
||
640 | 'src_chan' => $data['src_chan'], |
||
641 | ], |
||
642 | ]; |
||
643 | /** @var CallDetailRecordsTmp $m_data */ |
||
644 | /** @var CallDetailRecordsTmp $row */ |
||
645 | $m_data = CallDetailRecordsTmp::find($filter); |
||
646 | foreach ($m_data as $row) { |
||
647 | // Пробуем остановить записть разговора. |
||
648 | $this->StopMixMonitor($row->dst_chan); |
||
649 | $this->StopMixMonitor($row->src_chan); |
||
650 | // Установим признак переадресации. |
||
651 | $row->writeAttribute('transfer', 1); |
||
652 | $row->save(); |
||
653 | } |
||
654 | } |
||
655 | |||
656 | /** |
||
657 | * Обработка события создания канала - пары, при начале переадресации звонка. |
||
658 | * |
||
659 | * @param $data |
||
660 | */ |
||
661 | public function Action_transfer_dial_create_chan($data): void |
||
662 | { |
||
663 | $filter = [ |
||
664 | 'UNIQUEID=:UNIQUEID: AND endtime = "" AND answer = ""', |
||
665 | 'bind' => [ |
||
666 | 'UNIQUEID' => $data['transfer_UNIQUEID'], |
||
667 | ], |
||
668 | ]; |
||
669 | $row_create = false; |
||
670 | /** @var CallDetailRecordsTmp $m_data */ |
||
671 | /** @var CallDetailRecordsTmp $row */ |
||
672 | $m_data = CallDetailRecordsTmp::find($filter); |
||
673 | foreach ($m_data as $row) { |
||
674 | /// |
||
675 | // Проверим, если более одного канала SIP/256 при входящем. |
||
676 | if ( ! empty($row->dst_chan) && $data['dst_chan'] !== $row->dst_chan) { |
||
677 | if ($row_create) { |
||
678 | continue; |
||
679 | } |
||
680 | // Необходимо дублировать строку звонка. |
||
681 | $new_row = new CallDetailRecordsTmp(); |
||
682 | $f_list = $row->toArray(); |
||
683 | foreach ($f_list as $attribute => $value) { |
||
684 | if ($attribute === 'id') { |
||
685 | continue; |
||
686 | } |
||
687 | $new_row->writeAttribute($attribute, $value); |
||
688 | } |
||
689 | $new_row->writeAttribute('dst_chan', $data['dst_chan']); |
||
690 | $new_row->writeAttribute('UNIQUEID', $data['transfer_UNIQUEID'] . '_' . $data['dst_chan']); |
||
691 | // $new_row->save(); |
||
692 | // Подмена $row; |
||
693 | $row = $new_row; |
||
694 | $row_create = true; |
||
695 | } |
||
696 | |||
697 | $data['recordingfile'] = $this->MixMonitor($data['dst_chan'], $row->UNIQUEID, null, null, true); |
||
698 | // конец проверки |
||
699 | /// |
||
700 | |||
701 | $row->writeAttribute('dst_chan', $data['dst_chan']); |
||
702 | $row->writeAttribute('recordingfile', $data['recordingfile']); |
||
703 | if (isset($data['dst_call_id']) && ! empty($data['dst_call_id'])) { |
||
704 | $row->writeAttribute('dst_call_id', $data['dst_call_id']); |
||
705 | } |
||
706 | $res = $row->save(); |
||
707 | if ( ! $res) { |
||
708 | Util::sysLogMsg('Action_transfer_dial_create_chan', implode(' ', $row->getMessages())); |
||
709 | } |
||
710 | } |
||
711 | } |
||
712 | |||
713 | /** |
||
714 | * Обработка события ответа на переадресацию. Соединение абонентов. |
||
715 | * |
||
716 | * @param $data |
||
717 | */ |
||
718 | public function Action_transfer_dial_answer($data): void |
||
719 | { |
||
720 | $filter = [ |
||
721 | '(UNIQUEID=:UNIQUEID: OR UNIQUEID=:UNIQUEID_CHAN:) AND answer = "" AND endtime = ""', |
||
722 | 'bind' => [ |
||
723 | 'UNIQUEID' => $data['transfer_UNIQUEID'], |
||
724 | 'UNIQUEID_CHAN' => $data['transfer_UNIQUEID'] . '_' . $data['agi_channel'], |
||
725 | ], |
||
726 | ]; |
||
727 | |||
728 | /** @var CallDetailRecordsTmp $m_data */ |
||
729 | /** @var CallDetailRecordsTmp $row */ |
||
730 | $m_data = CallDetailRecordsTmp::find($filter); |
||
731 | foreach ($m_data as $row) { |
||
732 | $row->writeAttribute('answer', $data['answer']); |
||
733 | $res = $row->save(); |
||
734 | if ( ! $res) { |
||
735 | Util::sysLogMsg('Action_transfer_dial_answer', implode(' ', $row->getMessages())); |
||
736 | } |
||
737 | } |
||
738 | } |
||
739 | |||
740 | /** |
||
741 | * Завершение канала при прееадресации. |
||
742 | * |
||
743 | * @param $data |
||
744 | */ |
||
745 | public function Action_transfer_dial_hangup($data): void |
||
746 | { |
||
747 | $pos = stripos($data['agi_channel'], 'local/'); |
||
748 | if ($pos === false) { |
||
749 | // Это НЕ локальный канал. |
||
750 | // Если это завершение переадресации (консультативной). Создадим новую строку CDR. |
||
751 | $this->Action_CreateRowTransfer('transfer_dial_hangup', $data); |
||
752 | |||
753 | // Найдем записанные ранее строки. |
||
754 | $filter = [ |
||
755 | 'linkedid=:linkedid: AND endtime = "" AND (src_chan=:chan: OR dst_chan=:chan:)', |
||
756 | 'bind' => [ |
||
757 | 'linkedid' => $data['linkedid'], |
||
758 | 'chan' => $data['agi_channel'], |
||
759 | ], |
||
760 | ]; |
||
761 | /** @var CallDetailRecordsTmp $m_data */ |
||
762 | /** @var CallDetailRecordsTmp $row */ |
||
763 | $m_data = CallDetailRecordsTmp::find($filter); |
||
764 | foreach ($m_data as $row) { |
||
765 | // Завершим вызов в CDR. |
||
766 | $row->writeAttribute('endtime', $data['end']); |
||
767 | $row->writeAttribute('transfer', 0); |
||
768 | if ( ! $row->save()) { |
||
769 | Util::sysLogMsg('Action_transfer_dial_answer', implode(' ', $row->getMessages())); |
||
770 | } |
||
771 | } |
||
772 | // Попробуем начать запись разговора. |
||
773 | $filter = [ |
||
774 | 'linkedid=:linkedid: AND endtime = "" AND transfer=1', |
||
775 | 'bind' => [ |
||
776 | 'linkedid' => $data['linkedid'], |
||
777 | ], |
||
778 | ]; |
||
779 | /** @var CallDetailRecordsTmp $res */ |
||
780 | $res = CallDetailRecordsTmp::findFirst($filter); |
||
781 | if ($res !== null) { |
||
782 | $info = pathinfo($res->recordingfile); |
||
783 | $data_time = (empty($res->answer)) ? $res->start : $res->answer; |
||
784 | $subdir = date('Y/m/d/H/', strtotime($data_time)); |
||
785 | $this->MixMonitor($res->src_chan, $info['filename'], $subdir); |
||
786 | } |
||
787 | } elseif ('' === $data['ANSWEREDTIME']) { |
||
788 | $filter = [ |
||
789 | 'linkedid=:linkedid: AND endtime = "" AND (src_chan=:src_chan: OR dst_chan=:dst_chan:)', |
||
790 | 'bind' => [ |
||
791 | 'linkedid' => $data['linkedid'], |
||
792 | 'src_chan' => $data['TRANSFERERNAME'], |
||
793 | 'dst_chan' => $data['dst_chan'], |
||
794 | ], |
||
795 | ]; |
||
796 | /** @var CallDetailRecordsTmp $m_data */ |
||
797 | /** @var CallDetailRecordsTmp $row */ |
||
798 | $m_data = CallDetailRecordsTmp::find($filter); |
||
799 | foreach ($m_data as $row) { |
||
800 | // Ответа не было. Переадресация отменена. |
||
801 | $row->writeAttribute('endtime', $data['end']); |
||
802 | $row->writeAttribute('transfer', 0); |
||
803 | if ( ! $row->save()) { |
||
804 | Util::sysLogMsg('Action_transfer_dial_answer', implode(' ', $row->getMessages())); |
||
805 | } |
||
806 | } |
||
807 | |||
808 | // Попробуем возобновить запись разговора. |
||
809 | $filter = [ |
||
810 | 'linkedid=:linkedid: AND endtime = ""', |
||
811 | 'bind' => [ |
||
812 | 'linkedid' => $data['linkedid'], |
||
813 | ], |
||
814 | ]; |
||
815 | $m_data = CallDetailRecordsTmp::find($filter); |
||
816 | foreach ($m_data as $row) { |
||
817 | $info = pathinfo($row->recordingfile); |
||
818 | $data_time = ($row->answer == null) ? $row->start : $row->answer; |
||
819 | $subdir = date('Y/m/d/H/', strtotime($data_time)); |
||
820 | $this->MixMonitor($row->src_chan, $info['filename'], $subdir); |
||
821 | // Снимем со строк признак переадресации. |
||
822 | $row->writeAttribute('transfer', 0); |
||
823 | if ( ! $row->save()) { |
||
824 | Util::sysLogMsg('Action_transfer_dial_answer', implode(' ', $row->getMessages())); |
||
825 | } |
||
826 | } |
||
827 | } |
||
828 | } |
||
829 | |||
830 | /** |
||
831 | * Начало работы приложения. |
||
832 | * |
||
833 | * @param $data |
||
834 | */ |
||
835 | public function Action_dial_app($data): void |
||
836 | { |
||
837 | $this->Action_app_end($data); |
||
838 | $this->insertDataToDbM($data); |
||
839 | } |
||
840 | |||
841 | /** |
||
842 | * Вызов в нерабочее время. |
||
843 | * |
||
844 | * @param $data |
||
845 | */ |
||
846 | public function Action_dial_outworktimes($data): void |
||
847 | { |
||
848 | $this->insertDataToDbM($data); |
||
849 | } |
||
850 | |||
851 | /** |
||
852 | * Старт очереди. |
||
853 | * |
||
854 | * @param $data |
||
855 | */ |
||
856 | public function Action_queue_start($data): void |
||
857 | { |
||
858 | if ($data['transfer'] == '1') { |
||
859 | // Если это трансфер выполним поиск связанных данных. |
||
860 | $this->Action_transfer_check($data); |
||
861 | } |
||
862 | if (isset($data['start'])) { |
||
863 | // Это новая строка. |
||
864 | $this->insertDataToDbM($data); |
||
865 | } else { |
||
866 | // Требуется только обновление данных. |
||
867 | $this->updateDataInDbM($data); |
||
868 | } |
||
869 | $this->Action_app_end($data); |
||
870 | } |
||
871 | |||
872 | /** |
||
873 | * Событие входа в конференцкомнату. |
||
874 | * |
||
875 | * @param $data |
||
876 | */ |
||
877 | public function Action_meetme_dial($data): void |
||
910 | } |
||
911 | } |
||
912 | |||
913 | /** |
||
914 | * Снятие вызова с парковки. |
||
915 | * |
||
916 | * @param $data |
||
917 | */ |
||
918 | public function Action_unpark_call($data): void |
||
919 | { |
||
920 | $data['recordingfile'] = $this->MixMonitor($data['dst_chan'], $data['UNIQUEID']); |
||
921 | $this->insertDataToDbM($data); |
||
922 | if (is_array($data['data_parking'])) { |
||
923 | $this->insertDataToDbM($data['data_parking']); |
||
924 | } |
||
925 | $filter = [ |
||
926 | "linkedid=:linkedid: AND src_chan=:src_chan:", |
||
927 | 'bind' => [ |
||
928 | 'linkedid' => $data['linkedid_old'], |
||
929 | 'src_chan' => $data['agi_channel'], |
||
930 | ], |
||
931 | ]; |
||
932 | /** @var CallDetailRecordsTmp $m_data */ |
||
933 | /** @var CallDetailRecordsTmp $row */ |
||
934 | $m_data = CallDetailRecordsTmp::find($filter); |
||
935 | foreach ($m_data as $row) { |
||
936 | $row->delete(); |
||
937 | } |
||
938 | } |
||
939 | |||
940 | /** |
||
941 | * Возвращаем вызов с парковки по таймауту. |
||
942 | * |
||
943 | * @param $data |
||
944 | */ |
||
945 | public function Action_unpark_call_timeout($data): void |
||
946 | { |
||
947 | $this->insertDataToDbM($data); |
||
948 | } |
||
949 | |||
950 | /** |
||
951 | * Ответ агента очереди. |
||
952 | * |
||
953 | * @param $data |
||
954 | */ |
||
955 | public function Action_queue_answer($data): void |
||
956 | { |
||
957 | $filter = [ |
||
958 | 'UNIQUEID=:UNIQUEID: AND answer = ""', |
||
959 | 'bind' => [ |
||
960 | 'UNIQUEID' => $data['id'], |
||
961 | ], |
||
962 | ]; |
||
963 | /** @var CallDetailRecordsTmp $m_data */ |
||
964 | /** @var CallDetailRecordsTmp $row */ |
||
965 | $m_data = CallDetailRecordsTmp::find($filter); |
||
966 | foreach ($m_data as $row) { |
||
967 | $row->writeAttribute('answer', $data['answer']); |
||
968 | $row->writeAttribute('endtime', $data['answer']); |
||
969 | $res = $row->save(); |
||
970 | if ( ! $res) { |
||
971 | Util::sysLogMsg('Action_queue_answer', implode(' ', $row->getMessages())); |
||
972 | } |
||
973 | } |
||
974 | } |
||
975 | |||
976 | /** |
||
977 | * Завершение работы очереди. |
||
978 | * |
||
979 | * @param $data |
||
980 | */ |
||
981 | public function Action_queue_end($data): void |
||
982 | { |
||
983 | $filter = [ |
||
984 | "UNIQUEID=:UNIQUEID:", |
||
985 | 'bind' => [ |
||
986 | 'UNIQUEID' => $data['id'], |
||
987 | ], |
||
988 | ]; |
||
989 | /** @var CallDetailRecordsTmp $m_data */ |
||
990 | /** @var CallDetailRecordsTmp $row */ |
||
991 | $m_data = CallDetailRecordsTmp::find($filter); |
||
992 | foreach ($m_data as $row) { |
||
993 | $row->writeAttribute('endtime', $data['end']); |
||
994 | $row->writeAttribute('is_app', 1); |
||
995 | if ($data['dialstatus'] != '') { |
||
996 | $row->writeAttribute('dialstatus', $data['dialstatus']); |
||
997 | } |
||
998 | $res = $row->save(); |
||
999 | if ( ! $res) { |
||
1000 | Util::sysLogMsg('Action_queue_end', implode(' ', $row->getMessages())); |
||
1001 | } |
||
1002 | } |
||
1003 | } |
||
1004 | |||
1005 | /** |
||
1006 | * Завершение / уничтожение канала. |
||
1007 | * |
||
1008 | * @param $data |
||
1009 | */ |
||
1010 | public function Action_hangup_chan_meetme($data): void |
||
1011 | { |
||
1012 | clearstatcache(); |
||
1013 | $recordingfile = ''; |
||
1014 | $dest_chan = "MeetMe:{$data['conference']}"; |
||
1015 | // Отбираем все строки по текущей конференции. Не завершенные вызовы. |
||
1016 | $filter = [ |
||
1017 | 'dst_chan=:dst_chan: OR linkedid=:linkedid:', |
||
1018 | 'bind' => [ |
||
1019 | 'linkedid' => $data['linkedid'], |
||
1020 | 'dst_chan' => $dest_chan, |
||
1021 | ], |
||
1022 | ]; |
||
1023 | $m_data = CallDetailRecordsTmp::find($filter); |
||
1024 | /** @var CallDetailRecordsTmp $row */ |
||
1025 | foreach ($m_data as $row) { |
||
1026 | if ($dest_chan === $row->dst_chan) { |
||
1027 | $is_local = (stripos($data['src_chan'], 'local/') !== false); |
||
1028 | $is_stored_local = (stripos($row->src_chan, 'local/') !== false); |
||
1029 | if ($row->UNIQUEID === $data['UNIQUEID'] && $is_local && ! $is_stored_local) { |
||
1030 | $data['src_chan'] = $row->src_chan; |
||
1031 | } |
||
1032 | if (file_exists($row->recordingfile) || file_exists( |
||
1033 | Util::trimExtensionForFile($row->recordingfile) . '.wav' |
||
1034 | )) { |
||
1035 | // Переопределим путь к файлу записи разговора. Для конферецнии файл один. |
||
1036 | $recordingfile = $row->recordingfile; |
||
1037 | } |
||
1038 | } |
||
1039 | if ($row->linkedid === $data['meetme_id']) { |
||
1040 | continue; |
||
1041 | } |
||
1042 | // Подменим ID на идентификатор конференции. |
||
1043 | $row->writeAttribute('linkedid', $data['meetme_id']); |
||
1044 | $res = $row->save(); |
||
1045 | if ( ! $res) { |
||
1046 | Util::sysLogMsg('Action_hangup_chan_meetme', implode(' ', $row->getMessages())); |
||
1047 | } |
||
1048 | } |
||
1049 | |||
1050 | /** @var CallDetailRecordsTmp $m_data */ |
||
1051 | /** @var CallDetailRecordsTmp $row */ |
||
1052 | /** @var CallDetailRecordsTmp $rec_data */ |
||
1053 | foreach ($m_data as $row) { |
||
1054 | if ($row->src_chan !== $data['src_chan']) { |
||
1055 | continue; |
||
1056 | } |
||
1057 | // Заполняем данные для текущего оповещения. |
||
1058 | $row->writeAttribute('endtime', $data['end']); |
||
1059 | $row->writeAttribute('transfer', 0); |
||
1060 | $row->writeAttribute('linkedid', $data['meetme_id']); |
||
1061 | |||
1062 | if ( ! empty($recordingfile)) { |
||
1063 | $row->writeAttribute('recordingfile', $recordingfile); |
||
1064 | } |
||
1065 | $res = $row->save(); |
||
1066 | if ( ! $res) { |
||
1067 | Util::sysLogMsg('Action_hangup_chan_meetme', implode(' ', $row->getMessages())); |
||
1068 | } |
||
1069 | } |
||
1070 | } |
||
1071 | |||
1072 | /** |
||
1073 | * |
||
1074 | * @param $argv |
||
1075 | */ |
||
1076 | public function start($argv): void |
||
1077 | { |
||
1078 | $this->mixMonitorChannels = []; |
||
1079 | $this->checkChanHangupTransfer = []; |
||
1080 | $mikoPBXConfig = new MikoPBXConfig(); |
||
1081 | $this->record_calls = $mikoPBXConfig->getGeneralSettings('PBXRecordCalls') === '1'; |
||
1082 | $this->split_audio_thread = $mikoPBXConfig->getGeneralSettings('PBXSplitAudioThread') === '1'; |
||
1083 | $this->am = Util::getAstManager('off'); |
||
1084 | |||
1085 | // PID сохраняем при начале работы Worker. |
||
1086 | $client = new BeanstalkClient(self::class); |
||
1087 | $client->subscribe(self::class, [$this, 'callEventsWorker']); |
||
1088 | $client->subscribe(WorkerCdr::SELECT_CDR_TUBE, [$this, 'selectCDRWorker']); |
||
1089 | $client->subscribe(WorkerCdr::UPDATE_CDR_TUBE, [$this, 'updateCDRWorker']); |
||
1090 | $client->subscribe($this->makePingTubeName(self::class), [$this, 'pingCallBack']); |
||
1091 | $client->setErrorHandler([$this, 'errorHandler']); |
||
1092 | |||
1093 | while (true) { |
||
1094 | $client->wait(); |
||
1095 | } |
||
1096 | } |
||
1097 | |||
1098 | /** |
||
1099 | * Обработчик событий изменения состояния звонка. |
||
1100 | * |
||
1101 | * @param array | BeanstalkClient $tube |
||
1102 | */ |
||
1103 | public function callEventsWorker($tube): void |
||
1104 | { |
||
1105 | $data = json_decode($tube->getBody(), true); |
||
1106 | $funcName = "Action_".$data['action']??''; |
||
1107 | if ( method_exists($this, $funcName) ) { |
||
1108 | $this->$funcName($data); |
||
1109 | } |
||
1110 | $tube->reply(json_encode(true)); |
||
1111 | } |
||
1112 | |||
1113 | |||
1114 | /** |
||
1115 | * Получения CDR к обработке. |
||
1116 | * |
||
1117 | * @param array | BeanstalkClient $tube |
||
1118 | */ |
||
1119 | public function updateCDRWorker($tube): void |
||
1120 | { |
||
1121 | $q = $tube->getBody(); |
||
1122 | $data = json_decode($q, true); |
||
1123 | $res = $this->updateDataInDbM($data); |
||
1124 | $tube->reply(json_encode($res)); |
||
1125 | } |
||
1126 | |||
1127 | /** |
||
1128 | * Обновление данных в базе. |
||
1129 | * |
||
1130 | * @param $data |
||
1131 | * |
||
1132 | * @return bool |
||
1133 | */ |
||
1134 | public function updateDataInDbM($data): bool |
||
1189 | } |
||
1190 | |||
1191 | /** |
||
1192 | * Помещаем данные в базу используя модели. |
||
1193 | * |
||
1194 | * @param array $data |
||
1195 | * |
||
1196 | * @return bool |
||
1197 | * @throws Error |
||
1198 | */ |
||
1199 | public static function insertDataToDbM($data): bool |
||
1200 | { |
||
1201 | if (empty($data['UNIQUEID'])) { |
||
1202 | Util::sysLogMsg(__FUNCTION__, 'UNIQUEID is empty ' . json_encode($data)); |
||
1203 | |||
1204 | return false; |
||
1205 | } |
||
1206 | |||
1207 | $is_new = false; |
||
1208 | /** @var CallDetailRecordsTmp $m_data */ |
||
1209 | $m_data = CallDetailRecordsTmp::findFirst( |
||
1210 | [ |
||
1211 | "UNIQUEID=:id:", |
||
1212 | 'bind' => ['id' => $data['UNIQUEID'],], |
||
1213 | ] |
||
1214 | ); |
||
1215 | if ($m_data === null) { |
||
1216 | $m_data = new CallDetailRecordsTmp(); |
||
1217 | $is_new = true; |
||
1218 | } elseif (isset($data['IS_ORGNT']) && $data['IS_ORGNT'] !== false && $data['action'] == 'dial') { |
||
1219 | if (empty($m_data->endtime)) { |
||
1220 | // Если это оригинация dial может прийти дважды. |
||
1221 | if(!empty($m_data->src_num) && $m_data->src_num === $data['dst_num']){ |
||
1222 | $m_data->dst_num = $data['src_num']; |
||
1223 | $m_data->save(); |
||
1224 | } |
||
1225 | return true; |
||
1226 | } else { |
||
1227 | // Предыдущие звонки завершены. Текущий вызов новый, к примеру через резервного провайдера. |
||
1228 | // Меняем идентификатор предыдущих звонков. |
||
1229 | $m_data->UNIQUEID .= Util::generateRandomString(5); |
||
1230 | // Чистим путь к файлу записи. |
||
1231 | $m_data->recordingfile = ""; |
||
1232 | $m_data->save(); |
||
1233 | |||
1234 | $new_m_data = new CallDetailRecordsTmp(); |
||
1235 | $new_m_data->UNIQUEID = $data['UNIQUEID']; |
||
1236 | $new_m_data->start = $data['start']; |
||
1237 | $new_m_data->src_chan = $m_data->src_chan; |
||
1238 | $new_m_data->src_num = $m_data->src_num; |
||
1239 | $new_m_data->dst_num = $data['src_num']; |
||
1240 | $new_m_data->did = $data['did']; |
||
1241 | $new_m_data->from_account = $data['from_account']; |
||
1242 | $new_m_data->linkedid = $data['linkedid']; |
||
1243 | $new_m_data->transfer = $data['transfer']; |
||
1244 | |||
1245 | $res = $new_m_data->save(); |
||
1246 | if ( ! $res) { |
||
1247 | Util::sysLogMsg(__FUNCTION__, implode(' ', $m_data->getMessages())); |
||
1248 | } |
||
1249 | |||
1250 | return $res; |
||
1251 | } |
||
1252 | } |
||
1253 | |||
1254 | $f_list = $m_data->toArray(); |
||
1255 | foreach ($data as $attribute => $value) { |
||
1256 | if ( ! array_key_exists($attribute, $f_list)) { |
||
1257 | continue; |
||
1258 | } |
||
1259 | if ($is_new === false && 'UNIQUEID' == $attribute) { |
||
1260 | continue; |
||
1261 | } |
||
1262 | $m_data->writeAttribute($attribute, $value); |
||
1263 | } |
||
1264 | $res = $m_data->save(); |
||
1265 | if ( ! $res) { |
||
1266 | Util::sysLogMsg(__FUNCTION__, implode(' ', $m_data->getMessages())); |
||
1267 | } |
||
1268 | |||
1269 | return $res; |
||
1270 | } |
||
1271 | |||
1272 | /** |
||
1273 | * @param array | BeanstalkClient $tube |
||
1274 | */ |
||
1275 | public function selectCDRWorker($tube): void |
||
1276 | { |
||
1277 | $q = $tube->getBody(); |
||
1278 | $filter = json_decode($q, true); |
||
1279 | |||
1280 | if($this->filterNotValid($filter)){ |
||
1281 | $tube->reply('[]'); |
||
1282 | return; |
||
1283 | } |
||
1284 | |||
1285 | $res = null; |
||
1286 | try { |
||
1287 | if (isset($filter['miko_tmp_db'])) { |
||
1288 | $res = CallDetailRecordsTmp::find($filter); |
||
1289 | } else { |
||
1290 | $res = CallDetailRecords::find($filter); |
||
1291 | } |
||
1292 | $res_data = json_encode($res->toArray()); |
||
1293 | } catch (Error $e) { |
||
1294 | $res_data = '[]'; |
||
1295 | } |
||
1296 | |||
1297 | if ($res && isset($filter['add_pack_query'])) { |
||
1298 | $arr = []; |
||
1299 | foreach ($res->toArray() as $row) { |
||
1300 | $arr[] = $row[$filter['columns']]; |
||
1301 | } |
||
1302 | $filter['add_pack_query']['bind'][$filter['columns']] = $arr; |
||
1303 | |||
1304 | if($this->filterNotValid($filter['add_pack_query'])){ |
||
1305 | $tube->reply('[]'); |
||
1306 | return; |
||
1307 | } |
||
1308 | |||
1309 | try { |
||
1310 | $res = CallDetailRecords::find($filter['add_pack_query']); |
||
1311 | $res_data = json_encode($res->toArray(), JSON_THROW_ON_ERROR); |
||
1312 | } catch (Error $e) { |
||
1313 | $res_data = '[]'; |
||
1314 | } catch (\JsonException $e) { |
||
1315 | $res_data = '[]'; |
||
1316 | } |
||
1317 | } |
||
1318 | |||
1319 | if (isset($filter['miko_result_in_file'])) { |
||
1320 | $di = Di::getDefault(); |
||
1321 | $dirsConfig = $di->getShared('config'); |
||
1322 | $filename = $dirsConfig->path('core.tempDir') . '/' . md5(microtime(true)); |
||
1323 | file_put_contents($filename, $res_data); |
||
1324 | Util::addRegularWWWRights($filename); |
||
1325 | $res_data = json_encode($filename); |
||
1326 | } |
||
1327 | |||
1328 | $tube->reply($res_data); |
||
1329 | } |
||
1330 | |||
1331 | /** |
||
1332 | * Проверка фильтра на корректность bind параметров. |
||
1333 | * @param $filter |
||
1334 | */ |
||
1335 | private function filterNotValid($filter){ |
||
1349 | } |
||
1350 | |||
1351 | public function errorHandler($m): void |
||
1352 | { |
||
1353 | Util::sysLogMsg(self::class . '_ERROR', $m); |
||
1354 | } |
||
1355 | } |
||
1356 | |||
1357 | |||
1358 | // Start worker process |
||
1359 | $workerClassname = WorkerCallEvents::class; |
||
1360 | $action = $argv[1] ?? ''; |
||
1361 | if ($action === 'start') { |
||
1362 | cli_set_process_title($workerClassname); |
||
1363 | try { |
||
1364 | /** @var WorkerCallEvents $worker */ |
||
1365 | $worker = new $workerClassname(); |
||
1378 |