ActionHangupChan::hangupChanEndCalls()   F
last analyzed

Complexity

Conditions 23
Paths 1125

Size

Total Lines 100
Code Lines 65

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 65
c 1
b 0
f 0
dl 0
loc 100
rs 0
cc 23
nc 1125
nop 4

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * MikoPBX - free phone system for small business
5
 * Copyright © 2017-2023 Alexey Portnov and Nikolay Beketov
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License along with this program.
18
 * If not, see <https://www.gnu.org/licenses/>.
19
 */
20
21
namespace MikoPBX\Core\Workers\Libs\WorkerCallEvents;
22
23
use MikoPBX\Common\Models\CallDetailRecordsTmp;
24
use MikoPBX\Core\Asterisk\Configs\VoiceMailConf;
25
use MikoPBX\Core\System\SystemMessages;
26
use MikoPBX\Core\System\Util;
27
use MikoPBX\Core\Workers\WorkerCallEvents;
28
29
/**
30
 * Class ActionHangupChan
31
 *  This class handles the execution of a hangup actions.
32
 *
33
 *  @package MikoPBX\Core\Workers\Libs\WorkerCallEvents
34
 */
35
class ActionHangupChan
36
{
37
    /**
38
     * Executes the hangup channel action.
39
     *
40
     * @param WorkerCallEvents $worker The worker instance.
41
     * @param array $data The data containing call details.
42
     *
43
     * @return void
44
     * @throws \Exception
45
     */
46
    public static function execute(WorkerCallEvents $worker, array $data): void
47
    {
48
        // Remove the agi_channel from the active channels in the worker.
49
        $worker->removeActiveChan($data['agi_channel']);
50
51
        // Initialize arrays for channels and transfer calls.
52
        $channels = [];
53
        $transfer_calls = [];
54
55
        // Hangup channel for end calls.
56
        self::hangupChanEndCalls($worker, $data, $transfer_calls, $channels);
57
58
        // Check if it's a regular transfer.
59
        CreateRowTransfer::execute($worker, 'hangup_chan', $data, $transfer_calls);
60
61
        // Check if it's a SIP transfer.
62
        self::hangupChanCheckSipTrtansfer($worker, $data, $channels);
63
64
        // Clear memory.
65
        if (isset($worker->checkChanHangupTransfer[$data['agi_channel']])) {
66
            unset($worker->checkChanHangupTransfer[$data['agi_channel']]);
67
        }
68
        if (isset($worker->mixMonitorChannels[$data['agi_channel']])) {
69
            unset($worker->mixMonitorChannels[$data['agi_channel']]);
70
        }
71
    }
72
73
    /**
74
     * Hangs up the channel for end calls.
75
     *
76
     * @param WorkerCallEvents $worker The worker instance.
77
     * @param array $data The data containing call details.
78
     * @param array $transfer_calls The array to store transfer calls.
79
     * @param array $channels The array to store channels.
80
     *
81
     * @return void
82
     */
83
    private static function hangupChanEndCalls(WorkerCallEvents $worker, array $data, array &$transfer_calls, array &$channels): void
84
    {
85
        $filter = [
86
            'linkedid=:linkedid: AND endtime = ""',
87
            'bind' => [
88
                'linkedid' => $data['linkedid'],
89
            ],
90
        ];
91
        /** @var CallDetailRecordsTmp $m_data */
92
        /** @var CallDetailRecordsTmp $row */
93
        $m_data = CallDetailRecordsTmp::find($filter);
94
        $countRows = 0;
95
        $transferCdrAnswered = true;
96
        foreach ($m_data as $row) {
97
            if ($row->dst_chan !== $data['agi_channel'] && $data['agi_channel'] !== $row->src_chan) {
98
                continue;
99
            }
100
            $countRows++;
101
            $vmState = $data['VMSTATUS'] ?? '';
102
            if ($row->dst_num === VoiceMailConf::VOICE_MAIL_EXT && $vmState !== 'FAILED') {
103
                // This call will be ended by the voicemail_end event.
104
                continue;
105
            }
106
            if ($row->transfer === '1' && !empty($row->dst_chan)) {
107
                // Make sure the destination channel is not empty.
108
                // Otherwise, it's not a transfer.
109
                $transfer_calls[] = $row->toArray();
110
                $transferCdrAnswered = min($transferCdrAnswered, empty($row->answer));
111
            }
112
        }
113
        foreach ($m_data as $row) {
114
            if ($row->dst_chan !== $data['agi_channel'] && $data['agi_channel'] !== $row->src_chan) {
115
                continue;
116
            }
117
            $vmState = $data['VMSTATUS'] ?? '';
118
            if ($row->dst_num === VoiceMailConf::VOICE_MAIL_EXT && $vmState !== 'FAILED') {
119
                // This call will be ended by the voicemail_end event.
120
                continue;
121
            }
122
            if ($row->dialstatus === 'ORIGINATE') {
123
                $row->writeAttribute('dialstatus', '');
124
                if ($row->answer === '') {
125
                    $newId = $row->linkedid . '_' . $row->src_num . '_' . substr($row->src_chan, strpos($row->src_chan, '-') + 1);
126
                    $row->writeAttribute('UNIQUEID', $newId);
127
                }
128
            }
129
            $row->writeAttribute('endtime', $data['end']);
130
            $row->writeAttribute('transfer', 0);
131
            if ($transferCdrAnswered === false && count($transfer_calls) === 2) {
132
                // Call termination for consultative transfer. Initiator hung up before the destination answered.
133
                $row->writeAttribute('a_transfer', 1);
134
            }
135
            if ($data['dialstatus'] !== '') {
136
                if ($data['dialstatus'] === 'ORIGINATE') {
137
                    $row->writeAttribute('dst_chan', '');
138
                }
139
                $row->writeAttribute('dialstatus', $data['dialstatus']);
140
            }
141
            $res = $row->update();
142
            if (!$res) {
143
                SystemMessages::sysLogMsg('Action_hangup_chan', implode(' ', $row->getMessages()), LOG_DEBUG);
144
            }
145
146
            if ($row->src_chan !== $data['agi_channel']) {
147
                $channels[] = [
148
                    'chan' => $row->src_chan,
149
                    'did' => $row->did,
150
                    'num' => $row->src_num,
151
                    'out' => true,
152
                ];
153
            } else {
154
                $worker->StopMixMonitor($row->dst_chan, 'hangupChanEndCalls');
155
                $channels[] = [
156
                    'chan' => $row->dst_chan,
157
                    'did' => $row->did,
158
                    'num' => $row->dst_num,
159
                    'out' => false,
160
                ];
161
            }
162
        }
163
164
        // The SRC channel has been completed and DST channel has not been created
165
        $filter = [
166
            'verbose_call_id=:verbose_call_id: AND endtime = "" AND dst_chan = "" AND src_chan = :src_chan:',
167
            'bind' => [
168
                'verbose_call_id' => $data['verbose_call_id'],
169
                'src_chan' => $data['agi_channel'],
170
            ],
171
        ];
172
        $m_data = CallDetailRecordsTmp::find($filter);
173
        foreach ($m_data as $row) {
174
            $row->writeAttribute('endtime', $row->start);
175
            $row->writeAttribute('transfer', 0);
176
            $res = $row->update();
177
            if (!$res) {
178
                SystemMessages::sysLogMsg('Action_hangup_chan', implode(' ', $row->getMessages()), LOG_DEBUG);
179
            }
180
        }
181
182
        self::regMissedCall($data, $countRows);
183
    }
184
185
    /**
186
     * Registers a missed call.
187
     *
188
     * @param array $data The data containing call details.
189
     * @param int $tmpCdrCount The count of temporary CDRs.
190
     *
191
     * @return void
192
     */
193
    private static function regMissedCall(array $data, int $tmpCdrCount): void
194
    {
195
        if ($tmpCdrCount > 0 || $data['did'] === '') {
196
            return;
197
        }
198
        if (stripos($data['agi_channel'], 'local/') !== false) {
199
            // Do not log missed calls for local channels.
200
            return;
201
        }
202
        $filter = [
203
            'linkedid=:linkedid: AND (src_chan=:src_chan: OR dst_chan=:dst_chan:)',
204
            'bind' => [
205
                'linkedid' => $data['linkedid'],
206
                'src_chan' => $data['agi_channel'],
207
                'dst_chan' => $data['agi_channel'],
208
            ],
209
        ];
210
        $m_data = CallDetailRecordsTmp::findFirst($filter);
211
        if ($m_data !== null) {
212
            return;
213
        }
214
        if (empty($data['UNIQUEID'])) {
215
            $data['UNIQUEID'] = $data['agi_threadid'];
216
        }
217
        $time = intval(str_replace('mikopbx-', '', $data['linkedid']));
218
        $data['start'] = date("Y-m-d H:i:s.v", $time);
219
        $data['endtime'] = $data['end'];
220
221
        InsertDataToDB::execute($data, $data['agi_channel']);
222
    }
223
224
    /**
225
     * Checks for SIP transfers when hanging up a channel.
226
     *
227
     * @param WorkerCallEvents $worker The worker instance.
228
     * @param array $data The data containing call details.
229
     * @param array $channels The list of channels.
230
     *
231
     * @return void
232
     * @throws \Exception
233
     */
234
    public static function hangupChanCheckSipTrtansfer(WorkerCallEvents $worker, array $data, array $channels): void
235
    {
236
        $not_local = (stripos($data['agi_channel'], 'local/') === false);
237
        if ($not_local === false || $data['OLD_LINKEDID'] !== $data['linkedid']) {
238
            return;
239
        }
240
        $am = Util::getAstManager('off');
241
        $active_chans = $am->GetChannels(false);
242
243
        // Check for SIP transfers
244
        foreach ($channels as $data_chan) {
245
            if (!in_array($data_chan['chan'], $active_chans, true)) {
246
                // The call is already completed. Not interested.
247
                continue;
248
            }
249
            $BRIDGEPEER = $am->GetVar($data_chan['chan'], 'BRIDGEPEER', null, false);
250
            if (!is_string($BRIDGEPEER) || !in_array($BRIDGEPEER, $active_chans, true)) {
251
                // The call is already completed. Not interested.
252
                continue;
253
            }
254
255
            $linkedid = $am->GetVar($data_chan['chan'], 'CHANNEL(linkedid)', null, false);
256
            if (empty($linkedid) || $linkedid === $data['linkedid']) {
257
                continue;
258
            }
259
260
            $CALLERID = $am->GetVar($BRIDGEPEER, 'CALLERID(num)', null, false);
261
            $n_data['action'] = 'sip_transfer';
262
            $n_data['src_chan'] = $data_chan['out'] ? $data_chan['chan'] : $BRIDGEPEER;
263
            $n_data['src_num'] = $data_chan['out'] ? $data_chan['num'] : $CALLERID;
264
            $n_data['dst_chan'] = $data_chan['out'] ? $BRIDGEPEER : $data_chan['chan'];
265
            $n_data['dst_num'] = $data_chan['out'] ? $CALLERID : $data_chan['num'];
266
            $n_data['start'] = date('Y-m-d H:i:s');
267
            $n_data['answer'] = date('Y-m-d H:i:s');
268
            $n_data['linkedid'] = $linkedid;
269
            $n_data['UNIQUEID'] = $data['linkedid'] . Util::generateRandomString();
270
            $n_data['transfer'] = '0';
271
            if ($worker->enableMonitor($n_data['src_num'] ?? '', $n_data['dst_num'] ?? '')) {
272
                $n_data['recordingfile'] = $worker->MixMonitor($n_data['dst_chan'], $n_data['UNIQUEID'], '', '', 'hangupChanCheckSipTrtansfer');
273
            }
274
            $n_data['did'] = $data_chan['did'];
275
276
            InsertDataToDB::execute($n_data);
277
            $filter = [
278
                'linkedid=:linkedid:',
279
                'bind' => ['linkedid' => $data['linkedid']],
280
            ];
281
            $m_data = CallDetailRecordsTmp::find($filter);
282
            foreach ($m_data as $row) {
283
                $row->writeAttribute('linkedid', $linkedid);
284
                $row->save();
285
            }
286
287
            // Sending UserEvent
288
            $AgiData = base64_encode(json_encode($n_data));
289
            $am->UserEvent('CdrConnector', ['AgiData' => $AgiData]);
290
        }
291
    }
292
}
293