Passed
Push — develop ( 89a15e...42508e )
by Nikolay
05:59 queued 11s
created

ExtensionsController::saveSipCodecs()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 41
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 29
dl 0
loc 41
rs 8.2114
c 0
b 0
f 0
cc 8
nc 7
nop 1
1
<?php
2
/**
3
 * Copyright (C) MIKO LLC - All Rights Reserved
4
 * Unauthorized copying of this file, via any medium is strictly prohibited
5
 * Proprietary and confidential
6
 * Written by Nikolay Beketov, 6 2018
7
 *
8
 */
9
10
namespace MikoPBX\AdminCabinet\Controllers;
11
12
use MikoPBX\AdminCabinet\Forms\ExtensionEditForm;
13
use MikoPBX\Common\Models\{
14
    ExtensionForwardingRights,
15
    Extensions,
16
    ExternalPhones,
17
    NetworkFilters,
18
    PbxExtensionModules,
19
    PbxSettings,
20
    Sip,
21
    Users
22
};
23
use Phalcon\Text;
24
25
use function MikoPBX\Common\Config\appPath;
26
27
class ExtensionsController extends BaseController
28
{
29
30
    /**
31
     * Построение списка внутренних номеров и сотрудников
32
     */
33
    public function indexAction(): void
34
    {
35
        $extensionTable = [];
36
37
        $parameters = [
38
            'models'     => [
39
                'Extensions' => Extensions::class,
40
            ],
41
            'conditions' => 'Extensions.is_general_user_number = 1',
42
            'columns'    => [
43
                'id'       => 'Extensions.id',
44
                'username' => 'Users.username',
45
                'number'   => 'Extensions.number',
46
                'userid'   => 'Extensions.userid',
47
                'disabled' => 'Sip.disabled',
48
                'secret'   => 'Sip.secret',
49
                'email'    => 'Users.email',
50
                'type'     => 'Extensions.type',
51
                'avatar'   => 'Users.avatar',
52
53
            ],
54
            'order'      => 'number',
55
            'joins'      => [
56
                'Sip'   => [
57
                    0 => Sip::class,
58
                    1 => 'Sip.extension=Extensions.number',
59
                    2 => 'Sip',
60
                    3 => 'LEFT',
61
                ],
62
                'Users' => [
63
                    0 => Users::class,
64
                    1 => 'Users.id = Extensions.userid',
65
                    2 => 'Users',
66
                    3 => 'INNER',
67
                ],
68
            ],
69
        ];
70
        $query      = $this->modelsManager->createBuilder($parameters)->getQuery();
0 ignored issues
show
Bug introduced by
$parameters of type array<string,array<strin...<string,string>|string> is incompatible with the type string expected by parameter $params of Phalcon\Mvc\Model\Manage...erface::createBuilder(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

70
        $query      = $this->modelsManager->createBuilder(/** @scrutinizer ignore-type */ $parameters)->getQuery();
Loading history...
71
        $extensions = $query->execute();
72
73
        foreach ($extensions as $extension) {
74
            switch ($extension->type) {
75
                case 'SIP':
76
                    $extensionTable[$extension->userid]['userid']   = $extension->userid;
77
                    $extensionTable[$extension->userid]['number']   = $extension->number;
78
                    $extensionTable[$extension->userid]['status']   = ($extension->disabled === '1') ? 'disabled' : '';
79
                    $extensionTable[$extension->userid]['id']       = $extension->id;
80
                    $extensionTable[$extension->userid]['username'] = $extension->username;
81
                    $extensionTable[$extension->userid]['email']    = $extension->email;
82
                    $extensionTable[$extension->userid]['secret']   = $extension->secret;
83
84
                    if ( ! array_key_exists('mobile', $extensionTable[$extension->userid])) {
85
                        $extensionTable[$extension->userid]['mobile'] = '';
86
                    }
87
                    if ($extension->avatar) {
88
                        $filename    = md5($extension->avatar);
89
                        $imgCacheDir = appPath('sites/admin-cabinet/assets/img/cache');
90
                        $imgFile     = "{$imgCacheDir}/$filename.jpg";
91
                        if ( ! file_exists($imgFile)) {
92
                            $this->base64ToJpeg($extension->avatar, $imgFile);
93
                        }
94
95
                        $extensionTable[$extension->userid]['avatar'] = "{$this->url->get()}assets/img/cache/{$filename}.jpg";
96
                    } else {
97
                        $extensionTable[$extension->userid]['avatar'] = "{$this->url->get()}assets/img/unknownPerson.jpg";
98
                    }
99
100
                    break;
101
                case 'EXTERNAL':
102
                    $extensionTable[$extension->userid]['mobile'] = $extension->number;
103
                    break;
104
                default:
105
            }
106
        }
107
        $this->view->extensions = $extensionTable;
108
    }
109
110
    /**
111
     * Создает файл jpeg из переданной картинки
112
     *
113
     * @param $base64_string
114
     * @param $output_file
115
     *
116
     * @return mixed
117
     */
118
    private function base64ToJpeg($base64_string, $output_file)
119
    {
120
        // open the output file for writing
121
        $ifp = fopen($output_file, 'wb');
122
123
        // split the string on commas
124
        // $data[ 0 ] == "data:image/png;base64"
125
        // $data[ 1 ] == <actual base64 string>
126
        $data = explode(',', $base64_string);
127
128
        // we could add validation here with ensuring count( $data ) > 1
129
        fwrite($ifp, base64_decode($data[1]));
0 ignored issues
show
Bug introduced by
It seems like $ifp can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

129
        fwrite(/** @scrutinizer ignore-type */ $ifp, base64_decode($data[1]));
Loading history...
130
131
        // clean up the file resource
132
        fclose($ifp);
0 ignored issues
show
Bug introduced by
It seems like $ifp can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

132
        fclose(/** @scrutinizer ignore-type */ $ifp);
Loading history...
133
134
        return $output_file;
135
    }
136
137
    /**
138
     * @param null $id - идентификатор внутреннего номера
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $id is correct as it would always require null to be passed?
Loading history...
139
     */
140
    public function modifyAction($id = null): void
141
    {
142
        $extension = Extensions::findFirstById($id);
143
144
        if ($extension === null) {
145
            $extension                         = new Extensions();
146
            $extension->show_in_phonebook      = '1';
0 ignored issues
show
Documentation Bug introduced by
It seems like '1' of type string is incompatible with the declared type integer|null of property $show_in_phonebook.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
147
            $extension->public_access          = '0';
0 ignored issues
show
Documentation Bug introduced by
It seems like '0' of type string is incompatible with the declared type integer|null of property $public_access.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
148
            $extension->is_general_user_number = '1';
0 ignored issues
show
Documentation Bug introduced by
It seems like '1' of type string is incompatible with the declared type integer|null of property $is_general_user_number.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
149
            $extension->type                   = 'SIP';
150
            $extension->Sip                    = new Sip();
0 ignored issues
show
Bug Best Practice introduced by
The property Sip does not exist on MikoPBX\Common\Models\Extensions. Since you implemented __set, consider adding a @property annotation.
Loading history...
151
            $extension->Sip->disabled          = 0;
0 ignored issues
show
Bug Best Practice introduced by
The property Sip does not exist on MikoPBX\Common\Models\Extensions. Since you implemented __get, consider adding a @property annotation.
Loading history...
152
            $extension->Sip->type              = 'peer';
153
            $extension->Sip->uniqid            = strtoupper('SIP-PHONE-' . md5(time()));
154
            $extension->Sip->busylevel         = 1;
155
            $extension->Sip->qualify           = '1';
0 ignored issues
show
Documentation Bug introduced by
It seems like '1' of type string is incompatible with the declared type integer|null of property $qualify.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
156
            $extension->Sip->qualifyfreq       = 60;
157
            $extension->number                 = $this->getNextInternalNumber();
158
159
            $extension->Users       = new Users();
0 ignored issues
show
Bug Best Practice introduced by
The property Users does not exist on MikoPBX\Common\Models\Extensions. Since you implemented __set, consider adding a @property annotation.
Loading history...
160
            $extension->Users->role = 'user';
0 ignored issues
show
Bug Best Practice introduced by
The property Users does not exist on MikoPBX\Common\Models\Extensions. Since you implemented __get, consider adding a @property annotation.
Loading history...
161
162
            $extension->ExtensionForwardingRights = new ExtensionForwardingRights();
0 ignored issues
show
Bug Best Practice introduced by
The property ExtensionForwardingRights does not exist on MikoPBX\Common\Models\Extensions. Since you implemented __set, consider adding a @property annotation.
Loading history...
163
164
            $this->view->avatar        = '';
165
        } else {
166
            $this->view->avatar = $extension->Users->avatar;
167
        }
168
169
        $networkFilters            = NetworkFilters::getAllowedFiltersForType(['SIP']);
170
        $arrNetworkFilters['none'] = $this->translation->_('ex_NoNetworkFilter');
0 ignored issues
show
Comprehensibility Best Practice introduced by
$arrNetworkFilters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $arrNetworkFilters = array(); before regardless.
Loading history...
171
        foreach ($networkFilters as $filter) {
172
            $arrNetworkFilters[$filter->id] = $filter->getRepresent();
173
        }
174
175
        $parameters        = [
176
            'conditions' => 'type = "EXTERNAL" AND is_general_user_number = 1 AND userid=:userid:',
177
            'bind'       => [
178
                'userid' => $extension->userid,
179
            ],
180
        ];
181
        $externalExtension = Extensions::findFirst($parameters);
182
        if ($externalExtension === null) {
183
            $externalExtension                           = new Extensions();
184
            $externalExtension->userid                   = $extension->userid;
185
            $externalExtension->type                     = 'EXTERNAL';
186
            $externalExtension->is_general_user_number   = '1';
0 ignored issues
show
Documentation Bug introduced by
It seems like '1' of type string is incompatible with the declared type integer|null of property $is_general_user_number.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
187
            $externalExtension->ExternalPhones           = new ExternalPhones();
0 ignored issues
show
Bug Best Practice introduced by
The property ExternalPhones does not exist on MikoPBX\Common\Models\Extensions. Since you implemented __set, consider adding a @property annotation.
Loading history...
188
            $externalExtension->ExternalPhones->uniqid   = strtoupper('EXTERNAL-' . md5(time()));
0 ignored issues
show
Bug Best Practice introduced by
The property ExternalPhones does not exist on MikoPBX\Common\Models\Extensions. Since you implemented __get, consider adding a @property annotation.
Loading history...
189
            $externalExtension->ExternalPhones->disabled = '0';
0 ignored issues
show
Documentation Bug introduced by
It seems like '0' of type string is incompatible with the declared type integer|null of property $disabled.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
190
        }
191
192
193
        $forwardingExtensions[''] = $this->translation->_('ex_SelectNumber');
0 ignored issues
show
Comprehensibility Best Practice introduced by
$forwardingExtensions was never initialized. Although not strictly required by PHP, it is generally a good practice to add $forwardingExtensions = array(); before regardless.
Loading history...
194
195
        $parameters = [
196
            'conditions' => 'number IN ({ids:array})',
197
            'bind'       => [
198
                'ids' => [
199
                    $extension->ExtensionForwardingRights->forwarding,
0 ignored issues
show
Bug Best Practice introduced by
The property ExtensionForwardingRights does not exist on MikoPBX\Common\Models\Extensions. Since you implemented __get, consider adding a @property annotation.
Loading history...
200
                    $extension->ExtensionForwardingRights->forwardingonbusy,
201
                    $extension->ExtensionForwardingRights->forwardingonunavailable,
202
                ],
203
            ],
204
        ];
205
        $extensions = Extensions::find($parameters);
206
        foreach ($extensions as $record) {
207
            $forwardingExtensions[$record->number] = $record->getRepresent();
208
        }
209
210
        // Ограничим длинну внутреннего номера согласно настройкам
211
        $extensionsLength      = PbxSettings::getValueByKey('PBXInternalExtensionLength');
212
        $internalExtensionMask = '9{' . $extensionsLength . '}';
213
214
        $form = new ExtensionEditForm(
215
            $extension, [
216
                          'network_filters'        => $arrNetworkFilters,
217
                          'external_extension'     => $externalExtension,
218
                          'forwarding_extensions'  => $forwardingExtensions,
219
                          'internalextension_mask' => $internalExtensionMask,
220
                      ]
221
        );
222
223
        $this->view->form      = $form;
224
        $this->view->represent = $extension->getRepresent();
225
    }
226
227
    /**
228
     * Получает из базы следующий за последним введенным внутренним номером
229
     */
230
    private function getNextInternalNumber()
231
    {
232
        $parameters = [
233
            'conditions' => 'type = "SIP"',
234
            'column'     => 'number',
235
        ];
236
        $query      = Extensions::maximum($parameters);
237
        if ($query === null) {
238
            $query = 200;
239
        }
240
        $result       = (int)$query + 1;
241
        $extensionsLength
242
                      = PbxSettings::getValueByKey('PBXInternalExtensionLength');
243
        $maxExtension = (10 ** $extensionsLength) - 1;
244
245
        return ($result <= $maxExtension) ? $result : '';
246
    }
247
248
    /**
249
     * Сохранение карточки пользователя с его номерами
250
     *
251
     * @return void параметры помещаются в view и обрабатваются через ControllerBase::afterExecuteRoute()
252
     */
253
    public function saveAction(): void
254
    {
255
        if ( ! $this->request->isPost()) {
256
            return;
257
        }
258
259
        $this->db->begin();
260
261
        $data = $this->request->getPost();
262
263
        $sipEntity = false;
264
265
        if (array_key_exists('sip_uniqid', $data)) {
266
            $sipEntity = SIP::findFirstByUniqid($data['sip_uniqid']);
267
        }
268
269
        if ($sipEntity === null) {
270
            $sipEntity             = new SIP();
271
            $extension             = new Extensions();
272
            $userEntity            = new Users();
273
            $fwdEntity             = new ExtensionForwardingRights();
274
            $fwdEntity->ringlength = 45;
275
        } else {
276
            $extension = $sipEntity->Extensions;
277
            if ( ! $extension) {
278
                $extension = new Extensions();
279
            }
280
            $userEntity = $extension->Users;
281
            if ( ! $userEntity) {
282
                $userEntity = new Users();
283
            }
284
            $fwdEntity = $extension->ExtensionForwardingRights;
285
            if ( ! $fwdEntity) {
286
                $fwdEntity = new ExtensionForwardingRights();
287
            }
288
        }
289
290
        // Заполним параметры пользователя
291
        if ( ! $this->saveUser($userEntity, $data)) {
292
            $this->view->success = false;
293
            $this->db->rollback();
294
295
            return;
296
        }
297
298
        // Заполним параметры внутреннего номера
299
        if ( ! $this->saveExtension($extension, $userEntity, $data, false)) {
300
            $this->view->success = false;
301
            $this->db->rollback();
302
303
            return;
304
        }
305
306
        // Заполним параметры SIP учетки
307
        if ( ! $this->saveSip($sipEntity, $data)) {
0 ignored issues
show
Bug introduced by
It seems like $sipEntity can also be of type false; however, parameter $sipEntity of MikoPBX\AdminCabinet\Con...nsController::saveSip() does only seem to accept MikoPBX\Common\Models\Sip, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

307
        if ( ! $this->saveSip(/** @scrutinizer ignore-type */ $sipEntity, $data)) {
Loading history...
308
            $this->view->success = false;
309
            $this->db->rollback();
310
311
            return;
312
        }
313
314
315
        // Заполним параметры маршрутизации
316
        if ( ! $this->saveForwardingRights($fwdEntity, $data)) {
317
            $this->view->success = false;
318
            $this->db->rollback();
319
320
            return;
321
        }
322
323
        // Если мобильный не указан, то не будем его добавлять в базу
324
        if ( ! empty($data['mobile_number'])) {
325
            $externalPhone = ExternalPhones::findFirstByUniqid($data['mobile_uniqid']);
326
            if ($externalPhone === null) {
327
                $externalPhone   = new ExternalPhones();
328
                $mobileExtension = new Extensions();
329
            } else {
330
                $mobileExtension = $externalPhone->Extensions;
331
            }
332
333
            // Заполним параметры Extension для мобильного
334
            if ( ! $this->saveExtension($mobileExtension, $userEntity, $data, true)) {
335
                $this->view->success = false;
336
                $this->db->rollback();
337
338
                return;
339
            }
340
341
            // Заполним параметры ExternalPhones для мобильного
342
            if ( ! $this->saveExternalPhones($externalPhone, $data)) {
343
                $this->view->success = false;
344
                $this->db->rollback();
345
346
                return;
347
            }
348
        } else {
349
            // Удалить номер мобильного если он был привязан к пользователю
350
            $parameters          = [
351
                'conditions' => 'type="EXTERNAL" AND is_general_user_number = 1 AND userid=:userid:',
352
                'bind'       => [
353
                    'userid' => $userEntity->id,
354
                ],
355
            ];
356
            $deletedMobileNumber = Extensions::findFirst($parameters);
357
            if ($deletedMobileNumber !== null
358
                && $deletedMobileNumber->delete() === false) {
359
                $errors = $deletedMobileNumber->getMessages();
360
                $this->flash->error(implode('<br>', $errors));
361
                $this->view->success = false;
362
                $this->db->rollback();
363
364
                return;
365
            }
366
        }
367
368
        $this->flash->success($this->translation->_('ms_SuccessfulSaved'));
369
        $this->view->success = true;
370
        $this->db->commit();
371
372
        // Если это было создание карточки то надо перегрузить страницу с указанием ID
373
        if (empty($data['id'])) {
374
            $this->view->reload = "extensions/modify/{$extension->id}";
375
        }
376
    }
377
378
    /**
379
     * Сохранение параметров в таблицу Users
380
     *
381
     * @param Users $userEntity
382
     * @param array $data - POST дата
383
     *
384
     * @return bool результат сохранения
385
     */
386
    private function saveUser(Users $userEntity, $data)
387
    {
388
        // Заполним параметры пользователя
389
        foreach ($userEntity as $name => $value) {
390
            switch ($name) {
391
                case 'role':
392
                    if (array_key_exists('user_' . $name, $data)) {
393
                        $userEntity->$name = ($userEntity->$name === 'user') ? 'user' : $data['user_' . $name]; // не повышаем роль
394
                    }
395
                    break;
396
                default:
397
                    if (array_key_exists('user_' . $name, $data)) {
398
                        $userEntity->$name = $data['user_' . $name];
399
                    }
400
            }
401
        }
402
403
        if ($userEntity->save() === false) {
404
            $errors = $userEntity->getMessages();
405
            $this->flash->error(implode('<br>', $errors));
406
407
            return false;
408
        }
409
410
        return true;
411
    }
412
413
    /**
414
     * Сохранение параметров в таблицу Extensions
415
     *
416
     * @param Extensions $extension
417
     * @param Users      $userEntity
418
     * @param array      $data - POST дата
419
     * @param bool isMobile - это мобильный телефон
420
     *
421
     * @return bool результат сохранения
422
     */
423
    private function saveExtension(Extensions $extension, Users $userEntity, $data, $isMobile = false): bool
424
    {
425
        foreach ($extension as $name => $value) {
426
            switch ($name) {
427
                case 'id':
428
                    break;
429
                case 'show_in_phonebook':
430
                case 'is_general_user_number':
431
                    $extension->$name = '1';
432
                    break;
433
                case 'type':
434
                    $extension->$name = $isMobile ? 'EXTERNAL' : 'SIP';
435
                    break;
436
                case 'public_access':
437
                    if (array_key_exists($name, $data)) {
438
                        $extension->$name = ($data[$name] === 'on') ? '1' : '0';
439
                    } else {
440
                        $extension->$name = '0';
441
                    }
442
                    break;
443
                case 'callerid':
444
                    $extension->$name = $this->transliterate($data['user_username']);
445
                    break;
446
                case 'userid':
447
                    $extension->$name = $userEntity->id;
448
                    break;
449
                case 'number':
450
                    $extension->$name = $isMobile ? $data['mobile_number'] : $data['number'];
451
                    break;
452
                default:
453
                    if (array_key_exists($name, $data)) {
454
                        $extension->$name = $data[$name];
455
                    }
456
            }
457
        }
458
459
        if ($extension->save() === false) {
460
            $errors = $extension->getMessages();
461
            $this->flash->error(implode('<br>', $errors));
462
463
            return false;
464
        }
465
466
        return true;
467
    }
468
469
    /**
470
     * Сохранение параметров в таблицу SIP
471
     *
472
     * @param Sip   $sipEntity
473
     * @param array $data - POST дата
474
     *
475
     * @return bool результат сохранения
476
     */
477
    private function saveSip(Sip $sipEntity, $data): bool
478
    {
479
        foreach ($sipEntity as $name => $value) {
480
            switch ($name) {
481
                case 'qualify':
482
                    if (array_key_exists($name, $data)) {
483
                        $sipEntity->$name = ($data[$name] === 'on') ? '1' : '0';
484
                    } else {
485
                        $sipEntity->$name = "0";
486
                    }
487
                    break;
488
                case 'disabled':
489
                case 'disablefromuser':
490
                    if (array_key_exists('sip_' . $name, $data)) {
491
                        $sipEntity->$name = ($data['sip_' . $name] === 'on') ? '1' : '0';
492
                    } else {
493
                        $sipEntity->$name = "0";
494
                    }
495
                    break;
496
                case 'networkfilterid':
497
                    if ( ! array_key_exists('sip_' . $name, $data)) {
498
                        continue 2;
499
                    }
500
                    if ($data['sip_' . $name] === 'none') {
501
                        $sipEntity->$name = null;
502
                    } else {
503
                        $sipEntity->$name = $data['sip_' . $name];
504
                    }
505
                    break;
506
                case 'extension':
507
                    $sipEntity->$name = $data['number'];
508
                    break;
509
                case 'description':
510
                    $sipEntity->$name = $data['user_username'];
511
                    break;
512
                case 'manualattributes':
513
                    $sipEntity->setManualAttributes($data['sip_manualattributes']);
514
                    break;
515
                default:
516
                    if (array_key_exists('sip_' . $name, $data)) {
517
                        $sipEntity->$name = $data['sip_' . $name];
518
                    }
519
            }
520
        }
521
        if ($sipEntity->save() === false) {
522
            $errors = $sipEntity->getMessages();
523
            $this->flash->error(implode('<br>', $errors));
524
525
            return false;
526
        }
527
528
        return true;
529
    }
530
531
    /**
532
     * Заполним параметры переадресации
533
     *
534
     * @param \MikoPBX\Common\Models\ExtensionForwardingRights $forwardingRight
535
     * @param                                                  $data
536
     *
537
     * @return bool
538
     */
539
    private function saveForwardingRights(ExtensionForwardingRights $forwardingRight, $data): bool
540
    {
541
        foreach ($forwardingRight as $name => $value) {
542
            switch ($name) {
543
                case 'extension':
544
                    $forwardingRight->$name = $data['number'];
545
                    break;
546
                default:
547
                    if (array_key_exists('fwd_' . $name, $data)) {
548
                        $forwardingRight->$name = ($data['fwd_' . $name] === -1) ? '' : $data['fwd_' . $name];
549
                    }
550
            }
551
        }
552
        if (empty($forwardingRight->forwarding)) {
553
            $forwardingRight->ringlength = null;
554
        }
555
556
        if ($forwardingRight->save() === false) {
557
            $errors = $forwardingRight->getMessages();
558
            $this->flash->error(implode('<br>', $errors));
559
560
            return false;
561
        }
562
563
        return true;
564
    }
565
566
    /**
567
     * Заполним параметры ExternalPhones для мобильного номера
568
     *
569
     * @param ExternalPhones $externalPhone
570
     * @param array          $data - POST дата
571
     *
572
     * @return bool результат сохранения
573
     */
574
    private function saveExternalPhones(ExternalPhones $externalPhone, $data): bool
575
    {
576
        foreach ($externalPhone as $name => $value) {
577
            switch ($name) {
578
                case 'extension':
579
                    $externalPhone->$name = $data['mobile_number'];
580
                    break;
581
                case 'description':
582
                    $externalPhone->$name = $data['user_username'];
583
                    break;
584
                case 'disabled':
585
                    if (array_key_exists('mobile_' . $name, $data)) {
586
                        $externalPhone->$name = ($data['mobile_' . $name] === 'on') ? '1' : '0';
587
                    } else {
588
                        $externalPhone->$name = '0';
589
                    }
590
                    break;
591
                default:
592
                    if (array_key_exists('mobile_' . $name, $data)) {
593
                        $externalPhone->$name = $data['mobile_' . $name];
594
                    }
595
            }
596
        }
597
        if ($externalPhone->save() === false) {
598
            $errors = $externalPhone->getMessages();
599
            $this->flash->error(implode('<br>', $errors));
600
601
            return false;
602
        }
603
604
        return true;
605
    }
606
607
    /**
608
     * Удаление внутреннего номера и всех зависимых от него записей в том числе мобильного и переадресаций
609
     *
610
     * @param string $id - записи внутренненго номера
611
     */
612
    public function deleteAction($id = null)
613
    {
614
        $this->db->begin();
615
        $extension = Extensions::findFirstById($id);
616
617
        // Чтобы не было зацикливания при удалении сначала удалим
618
        // настройки переадресации у этой же учетной записи, т.к. она может ссылаться на себя
619
620
        $errors = null;
621
        if ($extension !== null && $extension->ExtensionForwardingRights
622
            && ! $extension->ExtensionForwardingRights->delete()) {
623
            $errors = $extension->ExtensionForwardingRights->getMessages();
624
        }
625
626
        if ( ! $errors && $extension) {
627
            $user = $extension->Users;
628
            if ( ! $user->delete()) {
629
                $errors = $user->getMessages();
630
            }
631
        }
632
633
        if ($errors) {
634
            $this->flash->error(implode('<br>', $errors));
635
            $this->db->rollback();
636
        } else {
637
            $this->db->commit();
638
        }
639
640
        $this->forward('extensions/index');
641
    }
642
643
    /**
644
     * Проверка на доступность номера JS скрипта extensions.js
645
     *
646
     * @param string $number - внутренний номер пользователя
647
     *
648
     * @return void параметры помещаются в view и обрабатваются через ControllerBase::afterExecuteRoute()
649
     */
650
    public function availableAction($number = null): void
651
    {
652
        $result = true;
653
        // Проверим пересечение с внутренним номерным планом
654
        $extension = Extensions::findFirstByNumber($number);
655
        if ($extension !== null) {
656
            $result             = false;
657
            $this->view->userId = $extension->userid;
658
        }
659
        // Проверим пересечение с парковочными слотами
660
        if ($result) {
661
            $parkExt       = PbxSettings::getValueByKey('PBXCallParkingExt');
662
            $parkStartSlot = PbxSettings::getValueByKey('PBXCallParkingStartSlot');
663
            $parkEndSlot   = PbxSettings::getValueByKey('PBXCallParkingEndSlot');
664
            if ($number === $parkExt || ($number >= $parkStartSlot && $number <= $parkEndSlot)) {
665
                $result             = false;
666
                $this->view->userId = 0;
667
            }
668
        }
669
670
        $this->view->numberAvailable = $result;
671
    }
672
673
    /**
674
     * Отключение всех номеров пользователя
675
     *
676
     * @param string $number - внутренний номер пользователя
677
     *
678
     * @return void
679
     */
680
    public function disableAction($number = null): void
681
    {
682
        $extension = Extensions::findFirstByNumber($number);
683
        if ($extension !== null) {
684
            $extensions = Extensions::findByUserid($extension->userid);
685
            foreach ($extensions as $extension) {
686
                switch ($extension->type) {
687
                    case 'SIP':
688
                        $extension->Sip->disabled = '1';
689
                        break;
690
                    case 'EXTERNAL':
691
                        $extension->ExternalPhones->disabled = '1';
692
                        break;
693
                }
694
                if ($extension->save() === true) {
695
                    $this->view->success = true;
696
                } else {
697
                    $this->view->success = false;
698
                    $errors              = $extension->getMessages();
699
                    $this->flash->error(implode('<br>', $errors));
700
701
                    return;
702
                }
703
            }
704
        }
705
    }
706
707
    /**
708
     * Включение всех номеров пользователя
709
     *
710
     * @param string $number - внутренний номер пользователя
711
     *
712
     * @return void
713
     */
714
    public function enableAction($number = null): void
715
    {
716
        $extension = Extensions::findFirstByNumber($number);
717
        if ($extension !== null) {
718
            $extensions = Extensions::findByUserid($extension->userid);
719
            foreach ($extensions as $extension) {
720
                switch ($extension->type) {
721
                    case 'SIP':
722
                        $extension->Sip->disabled = '0';
723
                        break;
724
                    case 'EXTERNAL':
725
                        $extension->ExternalPhones->disabled = '1';
726
                        break;
727
                }
728
                if ($extension->save() === true) {
729
                    $this->view->success = true;
730
                } else {
731
                    $this->view->success = false;
732
                    $errors              = $extension->getMessages();
733
                    $this->flash->error(implode('<br>', $errors));
734
735
                    return;
736
                }
737
            }
738
        }
739
    }
740
741
    /**
742
     * Возвращает представление для списка нормеров телефонов по AJAX запросу
743
     *
744
     * @return void
745
     */
746
    public function GetPhonesRepresentAction(): void
747
    {
748
        if ( ! $this->request->isPost()) {
749
            return;
750
        }
751
        $numbers = $this->request->getPost('numbers');
752
        $result  = [];
753
        foreach ($numbers as $number) {
754
            $result[$number] = [
755
                'number'    => $number,
756
                'represent' => $this->GetPhoneRepresentAction($number),
757
            ];
758
        }
759
        $this->view->success = true;
760
        $this->view->message = $result;
761
    }
762
763
    /**
764
     * Возвращает представление нормера телефона по AJAX запросу
765
     *
766
     * @param $phoneNumber
767
     *
768
     * @return string
769
     */
770
    public function GetPhoneRepresentAction($phoneNumber): string
771
    {
772
        $response = $phoneNumber;
773
774
        if (strlen($phoneNumber) > 10) {
775
            $seekNumber = substr($phoneNumber, -9);
776
            $parameters = [
777
                'conditions' => 'number LIKE :SearchPhrase1:',
778
                'bind'       => [
779
                    'SearchPhrase1' => "%{$seekNumber}",
780
                ],
781
            ];
782
        } else {
783
            $parameters = [
784
                'conditions' => 'number = :SearchPhrase1:',
785
                'bind'       => [
786
                    'SearchPhrase1' => $phoneNumber,
787
                ],
788
            ];
789
        }
790
        $result = Extensions::findFirst($parameters);
791
        if ($result !== null) {
792
            $response = $result->getRepresent();
793
        }
794
795
        return $response;
796
    }
797
798
    /**
799
     * Используется для генерации списка выбора пользователей из JS скрипта extensions.js
800
     *
801
     * @param string $type {all, phones, internal} - отображать только телефоны или все возможные номера
802
     *
803
     * @return void параметры помещаются в view и обрабатваются через ControllerBase::afterExecuteRoute()
804
     */
805
    public function getForSelectAction($type = 'all'): void
806
    {
807
        $results = [];
808
809
        switch ($type) {
810
            case 'all':
811
            {
812
                $parameters = [
813
                    'conditions' => 'show_in_phonebook="1"',
814
                ];
815
                break;
816
            }
817
            case 'phones':
818
            {
819
                // Список телефоонных эктеншенов
820
                $parameters = [
821
                    'conditions' => 'type IN ({ids:array}) AND show_in_phonebook="1"',
822
                    'bind'       => [
823
                        'ids' => ['SIP', 'EXTERNAL'],
824
                    ],
825
                ];
826
                break;
827
            }
828
            case 'internal':
829
            {
830
                // Только внутренние
831
                $parameters = [
832
                    'conditions' => 'type IN ({ids:array}) AND show_in_phonebook="1"',
833
                    'bind'       => [
834
                        'ids' => ['SIP'],
835
                    ],
836
                ];
837
                break;
838
            }
839
            default:
840
            {
841
                $parameters = [
842
                    'conditions' => 'show_in_phonebook="1"',
843
                ];
844
            }
845
        }
846
        $extensions = Extensions::find($parameters);
847
        foreach ($extensions as $record) {
848
            $type = ($record->userid > 0) ? ' USER'
849
                : $record->type; // Пользователи будут самыми первыми в списке
850
            $type = Text::underscore(strtoupper($type));
851
852
853
            // Необходимо проверить к какому модулю относится эта запись
854
            // и включен ли этот модуль в данный момент
855
            if ($type === 'MODULES') {
856
                $module = $this->findModuleByExtensionNumber($record->number);
857
                if ($module === null || $module->disabled === '1') {
858
                    continue; // исключаем отключенные модули
859
                }
860
            }
861
            $represent        = $record->getRepresent();
862
            $clearedRepresent = strip_tags($represent);
863
            $results[]        = [
864
                'name'          => $represent,
865
                'value'         => $record->number,
866
                'type'          => $type,
867
                'typeLocalized' => $this->translation->_("ex_dropdownCategory_{$type}"),
868
                'sorter'        => ($record->userid > 0) ? "{$type}{$clearedRepresent}{$record->number}" : "{$type}{$clearedRepresent}"
869
                // 'avatar' => ( $record->userid > 0 )
870
                // 	? $record->Users->avatar : '',
871
            ];
872
        }
873
874
        usort(
875
            $results,
876
            [__CLASS__, 'sortExtensionsArray']
877
        );
878
879
        $this->view->success = true;
880
        $this->view->results = $results;
881
    }
882
883
    /**
884
     * Сортировка массива extensions
885
     *
886
     * @param $a
887
     * @param $b
888
     *
889
     * @return int
890
     */
891
    private function sortExtensionsArray($a, $b): int
892
    {
893
        return strcmp($a['sorter'], $b['sorter']);
894
    }
895
896
897
    /**
898
     * Try to find module by extension number
899
     *
900
     * @param string $number
901
     *
902
     * @return mixed|null
903
     */
904
    private function findModuleByExtensionNumber(string $number)
905
    {
906
        $result         = null;
907
        $extension      = Extensions::findFirst("number ='{$number}'");
908
        $relatedLinks   = $extension->getRelatedLinks();
909
        $moduleUniqueID = false;
910
        foreach ($relatedLinks as $relation) {
911
            $obj = $relation['object'];
912
            if (strpos(get_class($obj), 'Modules\\') === 0) {
913
                $moduleUniqueID = explode('Models\\', get_class($obj))[1];
914
            }
915
        }
916
        if ($moduleUniqueID) {
917
            $result = PbxExtensionModules::findFirstByUniqid($moduleUniqueID);
918
        }
919
920
        return $result;
921
    }
922
923
924
}