Passed
Push — develop ( 3bb550...c283a9 )
by Nikolay
06:01 queued 11s
created

SystemManagementProcessor::uninstallModule()   A

Complexity

Conditions 5
Paths 28

Size

Total Lines 45
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

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