Passed
Push — master ( 6427d5...5783a1 )
by Nikolay
13:52 queued 05:42
created

SystemManagementProcessor::disableModule()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
c 0
b 0
f 0
dl 0
loc 16
rs 9.8666
cc 2
nc 2
nop 1
1
<?php
2
/*
3
 * Copyright © MIKO LLC - All Rights Reserved
4
 * Unauthorized copying of this file, via any medium is strictly prohibited
5
 * Proprietary and confidential
6
 * Written by Alexey Portnov, 9 2020
7
 */
8
9
namespace MikoPBX\PBXCoreREST\Lib;
10
11
12
use MikoPBX\Common\Models\AsteriskManagerUsers;
13
use MikoPBX\Common\Models\CallDetailRecords;
14
use MikoPBX\Common\Models\Extensions;
15
use MikoPBX\Common\Models\IncomingRoutingTable;
16
use MikoPBX\Common\Models\OutgoingRoutingTable;
17
use MikoPBX\Common\Models\OutWorkTimes;
18
use MikoPBX\Common\Models\PbxExtensionModules;
19
use MikoPBX\Common\Models\Providers;
20
use MikoPBX\Common\Models\SoundFiles;
21
use MikoPBX\Common\Providers\MessagesProvider;
22
use MikoPBX\Common\Providers\PBXConfModulesProvider;
23
use MikoPBX\Common\Providers\TranslationProvider;
24
use MikoPBX\Core\Asterisk\Configs\VoiceMailConf;
25
use MikoPBX\Core\System\Notifications;
26
use MikoPBX\Core\System\System;
27
use MikoPBX\Core\System\Util;
28
use MikoPBX\Modules\PbxExtensionState;
29
use MikoPBX\Modules\PbxExtensionUtils;
30
use MikoPBX\Modules\Setup\PbxExtensionSetupFailure;
31
use MikoPBX\PBXCoreREST\Workers\WorkerMergeUploadedFile;
32
use Phalcon\Di;
33
use Phalcon\Di\Injectable;
34
35
class SystemManagementProcessor extends Injectable
36
{
37
    /**
38
     * Processes System requests
39
     *
40
     * @param array $request
41
     *
42
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
43
     *
44
     * @throws \Exception
45
     */
46
    public static function callBack(array $request): PBXApiResult
47
    {
48
        $action         = $request['action'];
49
        $data           = $request['data'];
50
        $res            = new PBXApiResult();
51
        $res->processor = __METHOD__;
52
        switch ($action) {
53
            case 'reboot':
54
                System::rebootSync();
55
                $res->success = true;
56
                break;
57
            case 'shutdown':
58
                System::shutdown();
59
                $res->success = true;
60
                break;
61
            case 'getDate':
62
                $res->success           = true;
63
                $res->data['timestamp'] = time();
64
                break;
65
            case 'setDate':
66
                $res->success = System::setDate($data['timestamp'], $data['userTimeZone']);
67
                break;
68
            case 'updateMailSettings':
69
                $res->success = Notifications::sendTestMail();
70
                break;
71
            case 'sendMail':
72
                $res = self::sendMail($data);
73
                break;
74
            case 'unBanIp':
75
                $res = FirewallManagementProcessor::fail2banUnbanAll($data['ip']);
76
                break;
77
            case 'getBanIp':
78
                $res = FirewallManagementProcessor::getBanIp();
79
                break;
80
            case 'upgrade':
81
                $res = self::upgradeFromImg($data['temp_filename']);
82
                break;
83
            case 'installNewModule':
84
                $filePath = $request['data']['filePath'];
85
                $res      = self::installModuleFromFile($filePath);
86
                break;
87
            case 'enableModule':
88
                $moduleUniqueID = $request['data']['uniqid'];
89
                $res            = self::enableModule($moduleUniqueID);
90
                break;
91
            case 'disableModule':
92
                $moduleUniqueID = $request['data']['uniqid'];
93
                $res            = self::disableModule($moduleUniqueID);
94
                break;
95
            case 'uninstallModule':
96
                $moduleUniqueID = $request['data']['uniqid'];
97
                $keepSettings   = $request['data']['keepSettings'] === 'true';
98
                $res            = self::uninstallModule($moduleUniqueID, $keepSettings);
99
                break;
100
            case 'restoreDefault':
101
                $res = self::restoreDefaultSettings();
102
                break;
103
            case 'convertAudioFile':
104
                $mvPath = Util::which('mv');
105
                Util::mwExec("{$mvPath} {$request['data']['temp_filename']} {$request['data']['filename']}");
106
                $res = self::convertAudioFile($request['data']['filename']);
107
                break;
108
            default:
109
                $res             = new PBXApiResult();
110
                $res->processor  = __METHOD__;
111
                $res->messages[] = "Unknown action - {$action} in systemCallBack";
112
        }
113
114
        $res->function = $action;
115
116
        return $res;
117
    }
118
119
    /**
120
     * Sends test email to admin address
121
     *
122
     * @param                                       $data
123
     *
124
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult $res
125
     */
126
    private static function sendMail($data): PBXApiResult
127
    {
128
        $res            = new PBXApiResult();
129
        $res->processor = __METHOD__;
130
        if (isset($data['email']) && isset($data['subject']) && isset($data['body'])) {
131
            if (isset($data['encode']) && $data['encode'] === 'base64') {
132
                $data['subject'] = base64_decode($data['subject']);
133
                $data['body']    = base64_decode($data['body']);
134
            }
135
            $result = Notifications::sendMail($data['email'], $data['subject'], $data['body']);
136
            if ($result === true) {
137
                $res->success = true;
138
            } else {
139
                $res->success    = false;
140
                $res->messages[] = $result;
141
            }
142
        } else {
143
            $res->success    = false;
144
            $res->messages[] = 'Not all query parameters were set';
145
        }
146
147
        return $res;
148
    }
149
150
    /**
151
     * Upgrade MikoPBX from uploaded IMG file
152
     *
153
     * @param string $tempFilename path to uploaded image
154
     *
155
     * @return PBXApiResult
156
     */
157
    public static function upgradeFromImg(string $tempFilename): PBXApiResult
158
    {
159
        $res                  = new PBXApiResult();
160
        $res->processor       = __METHOD__;
161
        $res->success         = true;
162
        $res->data['message'] = 'In progress...';
163
164
165
        if ( ! file_exists($tempFilename)) {
166
            $res->success    = false;
167
            $res->messages[] = "Update file '{$tempFilename}' not found.";
168
169
            return $res;
170
        }
171
172
        if ( ! file_exists('/var/etc/cfdevice')) {
173
            $res->success    = false;
174
            $res->messages[] = "The system is not installed";
175
176
            return $res;
177
        }
178
        $dev = trim(file_get_contents('/var/etc/cfdevice'));
179
180
        $link = '/tmp/firmware_update.img';
181
        Util::createUpdateSymlink($tempFilename, $link);
182
        $mikopbx_firmwarePath = Util::which('mikopbx_firmware');
183
        Util::mwExecBg("{$mikopbx_firmwarePath} recover_upgrade {$link} /dev/{$dev}");
184
185
        return $res;
186
    }
187
188
    /**
189
     * Install new additional extension module
190
     *
191
     * @param $filePath
192
     *
193
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
194
     *
195
     */
196
    public static function installModuleFromFile($filePath): PBXApiResult
197
    {
198
        $res            = new PBXApiResult();
199
        $res->processor = __METHOD__;
200
        $moduleMetadata = FilesManagementProcessor::getMetadataFromModuleFile($filePath);
201
        if ( ! $moduleMetadata->success) {
202
            return $moduleMetadata;
203
        } else {
204
            $moduleUniqueID = $moduleMetadata->data['uniqid'];
205
            $res            = self::installModule($filePath, $moduleUniqueID);
206
        }
207
208
        return $res;
209
    }
210
211
    /**
212
     * Install module from file
213
     *
214
     * @param string $filePath
215
     *
216
     * @param string $moduleUniqueID
217
     *
218
     * @return PBXApiResult
219
     */
220
    public static function installModule(string $filePath, string $moduleUniqueID): PBXApiResult
221
    {
222
        $res              = new PBXApiResult();
223
        $res->processor   = __METHOD__;
224
        $res->success     = true;
225
        $currentModuleDir = PbxExtensionUtils::getModuleDir($moduleUniqueID);
226
        $needBackup       = is_dir($currentModuleDir);
227
228
        if ($needBackup) {
229
            self::uninstallModule($moduleUniqueID, true);
230
        }
231
232
        $semZaPath = Util::which('7za');
233
        Util::mwExec("{$semZaPath} e -spf -aoa -o{$currentModuleDir} {$filePath}");
234
        Util::addRegularWWWRights($currentModuleDir);
235
236
        $pbxExtensionSetupClass = "\\Modules\\{$moduleUniqueID}\\Setup\\PbxExtensionSetup";
237
        if (class_exists($pbxExtensionSetupClass)
238
            && method_exists($pbxExtensionSetupClass, 'installModule')) {
239
            $setup = new $pbxExtensionSetupClass($moduleUniqueID);
240
            if ( ! $setup->installModule()) {
241
                $res->success    = false;
242
                $res->messages[] = $setup->getMessages();
243
            }
244
        } else {
245
            $res->success    = false;
246
            $res->messages[] = "Install error: the class {$pbxExtensionSetupClass} not exists";
247
        }
248
249
        if ($res->success) {
250
            $res->data['needRestartWorkers'] = true;
251
        }
252
253
        return $res;
254
    }
255
256
    /**
257
     * Uninstall module
258
     *
259
     * @param string $moduleUniqueID
260
     *
261
     * @param bool   $keepSettings
262
     *
263
     * @return PBXApiResult
264
     */
265
    public static function uninstallModule(string $moduleUniqueID, bool $keepSettings): PBXApiResult
266
    {
267
        $res              = new PBXApiResult();
268
        $res->processor   = __METHOD__;
269
        $currentModuleDir = PbxExtensionUtils::getModuleDir($moduleUniqueID);
270
        // Kill all module processes
271
        if (is_dir("{$currentModuleDir}/bin")) {
272
            $busyboxPath = Util::which('busybox');
273
            $killPath    = Util::which('kill');
274
            $lsofPath    = Util::which('lsof');
275
            $grepPath    = Util::which('grep');
276
            $awkPath     = Util::which('awk');
277
            $uniqPath    = Util::which('uniq');
278
            Util::mwExec(
279
                "{$busyboxPath} {$killPath} -9 $({$lsofPath} {$currentModuleDir}/bin/* |  {$busyboxPath} {$grepPath} -v COMMAND | {$busyboxPath} {$awkPath}  '{ print $2}' | {$busyboxPath} {$uniqPath})"
280
            );
281
        }
282
        // Uninstall module with keep settings and backup db
283
        $moduleClass = "\\Modules\\{$moduleUniqueID}\\Setup\\PbxExtensionSetup";
284
285
        try {
286
            if (class_exists($moduleClass)
287
                && method_exists($moduleClass, 'uninstallModule')) {
288
                $setup = new $moduleClass($moduleUniqueID);
289
            } else {
290
                // Заглушка которая позволяет удалить модуль из базы данных, которого нет на диске
291
                $moduleClass = PbxExtensionSetupFailure::class;
292
                $setup       = new $moduleClass($moduleUniqueID);
293
            }
294
            $setup->uninstallModule($keepSettings);
295
        } finally {
296
            if (is_dir($currentModuleDir)) {
297
                // Broken or very old module. Force uninstall.
298
                $rmPath = Util::which('rm');
299
                Util::mwExec("{$rmPath} -rf {$currentModuleDir}");
300
301
                $moduleClass = PbxExtensionSetupFailure::class;
302
                $setup       = new $moduleClass($moduleUniqueID);
303
                $setup->unregisterModule();
304
            }
305
        }
306
        $res->success                    = true;
307
        $res->data['needRestartWorkers'] = true;
308
309
        return $res;
310
    }
311
312
    /**
313
     * Enables extension module
314
     *
315
     * @param string $moduleUniqueID
316
     *
317
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult $res
318
     */
319
    private static function enableModule(string $moduleUniqueID): PBXApiResult
320
    {
321
        $res                  = new PBXApiResult();
322
        $res->processor       = __METHOD__;
323
        $moduleStateProcessor = new PbxExtensionState($moduleUniqueID);
324
        if ($moduleStateProcessor->enableModule() === false) {
325
            $res->success  = false;
326
            $res->messages = $moduleStateProcessor->getMessages();
327
        } else {
328
            PBXConfModulesProvider::recreateModulesProvider();
329
            $res->data                       = $moduleStateProcessor->getMessages();
330
            $res->data['needRestartWorkers'] = true;
331
            $res->success                    = true;
332
        }
333
334
        return $res;
335
    }
336
337
    /**
338
     * Вшыфидуы extension module
339
     *
340
     * @param string $moduleUniqueID
341
     *
342
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult $res
343
     */
344
    private static function disableModule(string $moduleUniqueID): PBXApiResult
345
    {
346
        $res                  = new PBXApiResult();
347
        $res->processor       = __METHOD__;
348
        $moduleStateProcessor = new PbxExtensionState($moduleUniqueID);
349
        if ($moduleStateProcessor->disableModule() === false) {
350
            $res->success  = false;
351
            $res->messages = $moduleStateProcessor->getMessages();
352
        } else {
353
            PBXConfModulesProvider::recreateModulesProvider();
354
            $res->data                       = $moduleStateProcessor->getMessages();
355
            $res->data['needRestartWorkers'] = true; //TODO:: Проверить надо ли это
356
            $res->success                    = true;
357
        }
358
359
        return $res;
360
    }
361
362
    /**
363
     * Deletes all settings and uploaded files
364
     */
365
    private static function restoreDefaultSettings(): PBXApiResult
366
    {
367
        $res            = new PBXApiResult();
368
        $res->processor = __METHOD__;
369
        $di             = DI::getDefault();
370
        if ($di === null) {
371
            $res->messages[] = 'Error on DI initialize';
372
373
            return $res;
374
        }
375
376
        $res->success                    = true;
377
        $res->data['needRestartWorkers'] = true;
378
        $rm                              = Util::which('rm');
379
380
        // Pre delete some types
381
        $clearThisModels = [
382
            [Providers::class => ''],
383
            [OutgoingRoutingTable::class => ''],
384
            [IncomingRoutingTable::class => ''],
385
            [OutWorkTimes::class => ''],
386
            [AsteriskManagerUsers::class => ''],
387
            [Extensions::class => 'type="' . Extensions::TYPE_IVR_MENU . '"'],  // IVR Menu
388
            [Extensions::class => 'type="' . Extensions::TYPE_CONFERENCE . '"'],  // CONFERENCE
389
            [Extensions::class => 'type="' . Extensions::TYPE_QUEUE . '"'],  // QUEUE
390
        ];
391
392
        foreach ($clearThisModels as $modelParams) {
393
            foreach ($modelParams as $key => $value) {
394
                $records = call_user_func([$key, 'find'], $value);
395
                if ( ! $records->delete()) {
396
                    $res->messages[] = $records->getMessages();
397
                    $res->success    = false;
398
                }
399
            }
400
        }
401
402
        // Other extensions
403
        $parameters     = [
404
            'conditions' => 'not number IN ({ids:array})',
405
            'bind'       => [
406
                'ids' => [
407
                    '000063', // Reads back the extension
408
                    '000064', // 0000MILLI
409
                    '10003246',// Echo test
410
                    '10000100' // Voicemail
411
                ],
412
            ],
413
        ];
414
        $stopDeleting   = false;
415
        $countRecords   = Extensions::count($parameters);
416
        $deleteAttempts = 0;
417
        while ($stopDeleting === false) {
418
            $record = Extensions::findFirst($parameters);
419
            if ($record === null) {
420
                $stopDeleting = true;
421
                continue;
422
            }
423
            if ( ! $record->delete()) {
424
                $deleteAttempts += 1;
425
            }
426
            if ($deleteAttempts > $countRecords * 10) {
427
                $stopDeleting    = true; // Prevent loop
428
                $res->messages[] = $record->getMessages();
429
            }
430
        }
431
432
        // SoundFiles
433
        $parameters = [
434
            'conditions' => 'category = :custom:',
435
            'bind'       => [
436
                'custom' => SoundFiles::CATEGORY_CUSTOM,
437
            ],
438
        ];
439
        $records    = SoundFiles::find($parameters);
440
441
        foreach ($records as $record) {
442
            if (stripos($record->path, '/storage/usbdisk1/mikopbx') !== false) {
443
                Util::mwExec("{$rm} -rf {$record->path}");
444
                if ( ! $record->delete()) {
445
                    $res->messages[] = $record->getMessages();
446
                    $res->success    = false;
447
                }
448
            }
449
        }
450
451
        // PbxExtensions
452
        $records = PbxExtensionModules::find();
453
        foreach ($records as $record) {
454
            $moduleDir = PbxExtensionUtils::getModuleDir($record->uniqid);
455
            Util::mwExec("{$rm} -rf {$moduleDir}");
456
            if ( ! $record->delete()) {
457
                $res->messages[] = $record->getMessages();
458
                $res->success    = false;
459
            }
460
        }
461
462
        // Delete CallRecords
463
        $records = CallDetailRecords::find();
464
        if ( ! $records->delete()) {
465
            $res->messages[] = $records->getMessages();
466
            $res->success    = false;
467
        }
468
469
        // Delete CallRecords sound files
470
        $callRecordsPath = $di->getShared('config')->path('asterisk.monitordir');
471
        if (stripos($callRecordsPath, '/storage/usbdisk1/mikopbx') !== false) {
472
            Util::mwExec("{$rm} -rf {$callRecordsPath}/*");
473
        }
474
475
        return $res;
476
    }
477
478
    /**
479
     * Converts file to wav file with 8000 bitrate
480
     *
481
     * @param $filename
482
     *
483
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
484
     */
485
    public static function convertAudioFile($filename): PBXApiResult
486
    {
487
        $res            = new PBXApiResult();
488
        $res->processor = __METHOD__;
489
        if ( ! file_exists($filename)) {
490
            $res->success    = false;
491
            $res->messages[] = "File '{$filename}' not found.";
492
493
            return $res;
494
        }
495
        $out          = [];
496
        $tmp_filename = '/tmp/' . time() . "_" . basename($filename);
497
        if (false === copy($filename, $tmp_filename)) {
498
            $res->success    = false;
499
            $res->messages[] = "Unable to create temporary file '{$tmp_filename}'.";
500
501
            return $res;
502
        }
503
504
        // Принудительно устанавливаем расширение файла в wav.
505
        $n_filename     = Util::trimExtensionForFile($filename) . ".wav";
506
        $n_filename_mp3 = Util::trimExtensionForFile($filename) . ".mp3";
507
        // Конвертируем файл.
508
        $tmp_filename = escapeshellcmd($tmp_filename);
509
        $n_filename   = escapeshellcmd($n_filename);
510
        $soxPath      = Util::which('sox');
511
        Util::mwExec("{$soxPath} -v 0.99 -G '{$tmp_filename}' -c 1 -r 8000 -b 16 '{$n_filename}'", $out);
512
        $result_str = implode('', $out);
513
514
        $lamePath = Util::which('lame');
515
        Util::mwExec("{$lamePath} -b 32 --silent '{$n_filename}' '{$n_filename_mp3}'", $out);
516
        $result_mp3 = implode('', $out);
517
518
        // Чистим мусор.
519
        unlink($tmp_filename);
520
        if ($result_str !== '' && $result_mp3 !== '') {
521
            // Ошибка выполнения конвертации.
522
            $res->success    = false;
523
            $res->messages[] = $result_str;
524
525
            return $res;
526
        }
527
528
        if (file_exists($filename)
529
            && $filename !== $n_filename
530
            && $filename !== $n_filename_mp3) {
531
            unlink($filename);
532
        }
533
534
        $res->success = true;
535
        $res->data[]  = $n_filename_mp3;
536
537
        return $res;
538
    }
539
540
}