Passed
Push — develop ( b7c00c...da1f69 )
by Nikolay
08:07 queued 01:43
created

SystemManagementProcessor::disableModule()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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