Passed
Push — develop ( 6fe1b7...4c812d )
by Nikolay
05:04
created

SystemManagementProcessor   F

Complexity

Total Complexity 71

Size/Duplication

Total Lines 534
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 71
eloc 318
c 3
b 0
f 0
dl 0
loc 534
rs 2.7199

10 Methods

Rating   Name   Duplication   Size   Complexity  
B statusOfModuleInstallation() 0 35 6
C callBack() 0 75 17
F restoreDefaultSettings() 0 110 16
B sendMail() 0 22 7
A installModule() 0 44 5
A disableModule() 0 15 2
A uninstallModule() 0 44 5
A enableModule() 0 15 2
A upgradeFromImg() 0 29 3
B convertAudioFile() 0 53 8

How to fix   Complexity   

Complex Class

Complex classes like SystemManagementProcessor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SystemManagementProcessor, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright (C) 2017-2020 Alexey Portnov and Nikolay Beketov
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with this program.
17
 * If not, see <https://www.gnu.org/licenses/>.
18
 */
19
20
namespace MikoPBX\PBXCoreREST\Lib;
21
22
23
use MikoPBX\Common\Models\AsteriskManagerUsers;
24
use MikoPBX\Common\Models\CallDetailRecords;
25
use MikoPBX\Common\Models\Extensions;
26
use MikoPBX\Common\Models\IncomingRoutingTable;
27
use MikoPBX\Common\Models\OutgoingRoutingTable;
28
use MikoPBX\Common\Models\OutWorkTimes;
29
use MikoPBX\Common\Models\PbxExtensionModules;
30
use MikoPBX\Common\Models\Providers;
31
use MikoPBX\Common\Models\SoundFiles;
32
use MikoPBX\Common\Providers\PBXConfModulesProvider;
33
use MikoPBX\Core\System\Notifications;
34
use MikoPBX\Core\System\Processes;
35
use MikoPBX\Core\System\Storage;
36
use MikoPBX\Core\System\System;
37
use MikoPBX\Core\System\Util;
38
use MikoPBX\Modules\PbxExtensionState;
39
use MikoPBX\Modules\PbxExtensionUtils;
40
use MikoPBX\Modules\Setup\PbxExtensionSetupFailure;
41
use MikoPBX\PBXCoreREST\Workers\WorkerModuleInstaller;
42
use Phalcon\Di;
43
use Phalcon\Di\Injectable;
44
45
class SystemManagementProcessor extends Injectable
46
{
47
    /**
48
     * Processes System requests
49
     *
50
     * @param array $request
51
     *
52
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
53
     *
54
     * @throws \Exception
55
     */
56
    public static function callBack(array $request): PBXApiResult
57
    {
58
        $action         = $request['action'];
59
        $data           = $request['data'];
60
        $res            = new PBXApiResult();
61
        $res->processor = __METHOD__;
62
        switch ($action) {
63
            case 'reboot':
64
                System::rebootSync();
65
                $res->success = true;
66
                break;
67
            case 'shutdown':
68
                System::shutdown();
69
                $res->success = true;
70
                break;
71
            case 'getDate':
72
                $res->success           = true;
73
                $res->data['timestamp'] = time();
74
                break;
75
            case 'setDate':
76
                $res->success = System::setDate($data['timestamp'], $data['userTimeZone']);
77
                break;
78
            case 'updateMailSettings':
79
                $res->success = Notifications::sendTestMail();
80
                break;
81
            case 'sendMail':
82
                $res = self::sendMail($data);
83
                break;
84
            case 'unBanIp':
85
                $res = FirewallManagementProcessor::fail2banUnbanAll($data['ip']);
86
                break;
87
            case 'getBanIp':
88
                $res = FirewallManagementProcessor::getBanIp();
89
                break;
90
            case 'upgrade':
91
                $res = self::upgradeFromImg($data['temp_filename']);
92
                break;
93
            case 'installNewModule':
94
                $filePath = $request['data']['filePath'];
95
                $res      = self::installModule($filePath);
96
                break;
97
            case 'statusOfModuleInstallation':
98
                $filePath = $request['data']['filePath'];
99
                $res      = self::statusOfModuleInstallation($filePath);
100
                break;
101
            case 'enableModule':
102
                $moduleUniqueID = $request['data']['uniqid'];
103
                $res            = self::enableModule($moduleUniqueID);
104
                break;
105
            case 'disableModule':
106
                $moduleUniqueID = $request['data']['uniqid'];
107
                $res            = self::disableModule($moduleUniqueID);
108
                break;
109
            case 'uninstallModule':
110
                $moduleUniqueID = $request['data']['uniqid'];
111
                $keepSettings   = $request['data']['keepSettings'] === 'true';
112
                $res            = self::uninstallModule($moduleUniqueID, $keepSettings);
113
                break;
114
            case 'restoreDefault':
115
                $res = self::restoreDefaultSettings();
116
                break;
117
            case 'convertAudioFile':
118
                $mvPath = Util::which('mv');
119
                Processes::mwExec("{$mvPath} {$request['data']['temp_filename']} {$request['data']['filename']}");
120
                $res = self::convertAudioFile($request['data']['filename']);
121
                break;
122
            default:
123
                $res             = new PBXApiResult();
124
                $res->processor  = __METHOD__;
125
                $res->messages[] = "Unknown action - {$action} in systemCallBack";
126
        }
127
128
        $res->function = $action;
129
130
        return $res;
131
    }
132
133
    /**
134
     * Sends test email to admin address
135
     *
136
     * @param                                       $data
137
     *
138
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult $res
139
     */
140
    private static function sendMail($data): PBXApiResult
141
    {
142
        $res            = new PBXApiResult();
143
        $res->processor = __METHOD__;
144
        if (isset($data['email']) && isset($data['subject']) && isset($data['body'])) {
145
            if (isset($data['encode']) && $data['encode'] === 'base64') {
146
                $data['subject'] = base64_decode($data['subject']);
147
                $data['body']    = base64_decode($data['body']);
148
            }
149
            $result = Notifications::sendMail($data['email'], $data['subject'], $data['body']);
150
            if ($result === true) {
151
                $res->success = true;
152
            } else {
153
                $res->success    = false;
154
                $res->messages[] = 'Notifications::sendMail method returned false';
155
            }
156
        } else {
157
            $res->success    = false;
158
            $res->messages[] = 'Not all query parameters were set';
159
        }
160
161
        return $res;
162
    }
163
164
    /**
165
     * Upgrade MikoPBX from uploaded IMG file
166
     *
167
     * @param string $tempFilename path to uploaded image
168
     *
169
     * @return PBXApiResult
170
     */
171
    public static function upgradeFromImg(string $tempFilename): PBXApiResult
172
    {
173
        $res                  = new PBXApiResult();
174
        $res->processor       = __METHOD__;
175
        $res->success         = true;
176
        $res->data['message'] = 'In progress...';
177
178
179
        if ( ! file_exists($tempFilename)) {
180
            $res->success    = false;
181
            $res->messages[] = "Update file '{$tempFilename}' not found.";
182
183
            return $res;
184
        }
185
186
        if ( ! file_exists('/var/etc/cfdevice')) {
187
            $res->success    = false;
188
            $res->messages[] = "The system is not installed";
189
190
            return $res;
191
        }
192
        $dev = trim(file_get_contents('/var/etc/cfdevice'));
193
194
        $link = '/tmp/firmware_update.img';
195
        Util::createUpdateSymlink($tempFilename, $link);
196
        $mikoPBXFirmwarePath = Util::which('mikopbx_firmware');
197
        Processes::mwExecBg("{$mikoPBXFirmwarePath} recover_upgrade {$link} /dev/{$dev}");
198
199
        return $res;
200
    }
201
202
    /**
203
     * Install new additional extension module
204
     *
205
     * @param $filePath
206
     *
207
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
208
     *
209
     */
210
    public static function installModule($filePath): PBXApiResult
211
    {
212
        $res            = new PBXApiResult();
213
        $res->processor = __METHOD__;
214
        $resModuleMetadata = FilesManagementProcessor::getMetadataFromModuleFile($filePath);
215
        if ( ! $resModuleMetadata->success) {
216
            return $resModuleMetadata;
217
        } else {
218
            $moduleUniqueID = $resModuleMetadata->data['uniqid'];
219
            // If it enabled send disable action first
220
            if (PbxExtensionUtils::isEnabled($moduleUniqueID)){
221
                $res = self::disableModule($moduleUniqueID);
222
                if (!$res->success){
223
                    return $res;
224
                }
225
            }
226
227
            $currentModuleDir = PbxExtensionUtils::getModuleDir($moduleUniqueID);
228
            $needBackup       = is_dir($currentModuleDir);
229
230
            if ($needBackup) {
231
                self::uninstallModule($moduleUniqueID, true);
232
            }
233
234
            // We will start the background process to install module
235
            $temp_dir            = dirname($filePath);
236
            $install_settings = [
237
                'filePath' => $filePath,
238
                'currentModuleDir' => $currentModuleDir,
239
                'uniqid' => $moduleUniqueID,
240
            ];
241
            $settings_file  = "{$temp_dir}/install_settings.json";
242
            file_put_contents(
243
                $settings_file,
244
                json_encode($install_settings, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)
245
            );
246
            $phpPath               = Util::which('php');
247
            $workerFilesMergerPath = Util::getFilePathByClassName(WorkerModuleInstaller::class);
248
            Processes::mwExecBg("{$phpPath} -f {$workerFilesMergerPath} start '{$settings_file}'");
249
            $res->data['filePath']= $filePath;
250
            $res->success = true;
251
        }
252
253
        return $res;
254
    }
255
256
257
    /**
258
     * Uninstall module
259
     *
260
     * @param string $moduleUniqueID
261
     *
262
     * @param bool   $keepSettings
263
     *
264
     * @return PBXApiResult
265
     */
266
    public static function uninstallModule(string $moduleUniqueID, bool $keepSettings): PBXApiResult
267
    {
268
        $res              = new PBXApiResult();
269
        $res->processor   = __METHOD__;
270
        $currentModuleDir = PbxExtensionUtils::getModuleDir($moduleUniqueID);
271
        // Kill all module processes
272
        if (is_dir("{$currentModuleDir}/bin")) {
273
            $busyboxPath = Util::which('busybox');
274
            $killPath    = Util::which('kill');
275
            $lsofPath    = Util::which('lsof');
276
            $grepPath    = Util::which('grep');
277
            $awkPath     = Util::which('awk');
278
            $uniqPath    = Util::which('uniq');
279
            Processes::mwExec(
280
                "{$busyboxPath} {$killPath} -9 $({$lsofPath} {$currentModuleDir}/bin/* |  {$busyboxPath} {$grepPath} -v COMMAND | {$busyboxPath} {$awkPath}  '{ print $2}' | {$busyboxPath} {$uniqPath})"
281
            );
282
        }
283
        // Uninstall module with keep settings and backup db
284
        $moduleClass = "\\Modules\\{$moduleUniqueID}\\Setup\\PbxExtensionSetup";
285
286
        try {
287
            if (class_exists($moduleClass)
288
                && method_exists($moduleClass, 'uninstallModule')) {
289
                $setup = new $moduleClass($moduleUniqueID);
290
            } else {
291
                // Заглушка которая позволяет удалить модуль из базы данных, которого нет на диске
292
                $moduleClass = PbxExtensionSetupFailure::class;
293
                $setup       = new $moduleClass($moduleUniqueID);
294
            }
295
            $setup->uninstallModule($keepSettings);
296
        } finally {
297
            if (is_dir($currentModuleDir)) {
298
                // Broken or very old module. Force uninstall.
299
                $rmPath = Util::which('rm');
300
                Processes::mwExec("{$rmPath} -rf {$currentModuleDir}");
301
302
                $moduleClass = PbxExtensionSetupFailure::class;
303
                $setup       = new $moduleClass($moduleUniqueID);
304
                $setup->unregisterModule();
305
            }
306
        }
307
        $res->success = 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->success = true;
331
        }
332
333
        return $res;
334
    }
335
336
    /**
337
     * Disables extension module
338
     *
339
     * @param string $moduleUniqueID
340
     *
341
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult $res
342
     */
343
    private static function disableModule(string $moduleUniqueID): PBXApiResult
344
    {
345
        $res                  = new PBXApiResult();
346
        $res->processor       = __METHOD__;
347
        $moduleStateProcessor = new PbxExtensionState($moduleUniqueID);
348
        if ($moduleStateProcessor->disableModule() === false) {
349
            $res->success  = false;
350
            $res->messages = $moduleStateProcessor->getMessages();
351
        } else {
352
            PBXConfModulesProvider::recreateModulesProvider();
353
            $res->data    = $moduleStateProcessor->getMessages();
354
            $res->success = true;
355
        }
356
357
        return $res;
358
    }
359
360
    /**
361
     * Deletes all settings and uploaded files
362
     */
363
    private static function restoreDefaultSettings(): PBXApiResult
364
    {
365
        $res            = new PBXApiResult();
366
        $res->processor = __METHOD__;
367
        $di             = DI::getDefault();
368
        if ($di === null) {
369
            $res->messages[] = 'Error on DI initialize';
370
371
            return $res;
372
        }
373
374
        $res->success = true;
375
        $rm           = Util::which('rm');
376
377
        // Pre delete some types
378
        $clearThisModels = [
379
            [Providers::class => ''],
380
            [OutgoingRoutingTable::class => ''],
381
            [IncomingRoutingTable::class => ''],
382
            [OutWorkTimes::class => ''],
383
            [AsteriskManagerUsers::class => ''],
384
            [Extensions::class => 'type="' . Extensions::TYPE_IVR_MENU . '"'],  // IVR Menu
385
            [Extensions::class => 'type="' . Extensions::TYPE_CONFERENCE . '"'],  // CONFERENCE
386
            [Extensions::class => 'type="' . Extensions::TYPE_QUEUE . '"'],  // QUEUE
387
        ];
388
389
        foreach ($clearThisModels as $modelParams) {
390
            foreach ($modelParams as $key => $value) {
391
                $records = call_user_func([$key, 'find'], $value);
392
                if ( ! $records->delete()) {
393
                    $res->messages[] = $records->getMessages();
394
                    $res->success    = false;
395
                }
396
            }
397
        }
398
399
        // Other extensions
400
        $parameters     = [
401
            'conditions' => 'not number IN ({ids:array})',
402
            'bind'       => [
403
                'ids' => [
404
                    '000063', // Reads back the extension
405
                    '000064', // 0000MILLI
406
                    '10003246',// Echo test
407
                    '10000100' // Voicemail
408
                ],
409
            ],
410
        ];
411
        $stopDeleting   = false;
412
        $countRecords   = Extensions::count($parameters);
413
        $deleteAttempts = 0;
414
        while ($stopDeleting === false) {
415
            $record = Extensions::findFirst($parameters);
416
            if ($record === null) {
417
                $stopDeleting = true;
418
                continue;
419
            }
420
            if ( ! $record->delete()) {
421
                $deleteAttempts += 1;
422
            }
423
            if ($deleteAttempts > $countRecords * 10) {
424
                $stopDeleting    = true; // Prevent loop
425
                $res->messages[] = $record->getMessages();
426
            }
427
        }
428
429
        // SoundFiles
430
        $parameters = [
431
            'conditions' => 'category = :custom:',
432
            'bind'       => [
433
                'custom' => SoundFiles::CATEGORY_CUSTOM,
434
            ],
435
        ];
436
        $records    = SoundFiles::find($parameters);
437
438
        foreach ($records as $record) {
439
            if (stripos($record->path, '/storage/usbdisk1/mikopbx') !== false) {
440
                Processes::mwExec("{$rm} -rf {$record->path}");
441
                if ( ! $record->delete()) {
442
                    $res->messages[] = $record->getMessages();
443
                    $res->success    = false;
444
                }
445
            }
446
        }
447
448
        // PbxExtensions
449
        $records = PbxExtensionModules::find();
450
        foreach ($records as $record) {
451
            $moduleDir = PbxExtensionUtils::getModuleDir($record->uniqid);
452
            Processes::mwExec("{$rm} -rf {$moduleDir}");
453
            if ( ! $record->delete()) {
454
                $res->messages[] = $record->getMessages();
455
                $res->success    = false;
456
            }
457
        }
458
459
        // Delete CallRecords
460
        $records = CallDetailRecords::find();
461
        if ( ! $records->delete()) {
462
            $res->messages[] = $records->getMessages();
463
            $res->success    = false;
464
        }
465
466
        // Delete CallRecords sound files
467
        $callRecordsPath = $di->getShared('config')->path('asterisk.monitordir');
468
        if (stripos($callRecordsPath, '/storage/usbdisk1/mikopbx') !== false) {
469
            Processes::mwExec("{$rm} -rf {$callRecordsPath}/*");
470
        }
471
472
        return $res;
473
    }
474
475
    /**
476
     * Converts file to wav file with 8000 bitrate
477
     *
478
     * @param $filename
479
     *
480
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
481
     */
482
    public static function convertAudioFile($filename): PBXApiResult
483
    {
484
        $res            = new PBXApiResult();
485
        $res->processor = __METHOD__;
486
        if ( ! file_exists($filename)) {
487
            $res->success    = false;
488
            $res->messages[] = "File '{$filename}' not found.";
489
490
            return $res;
491
        }
492
        $out          = [];
493
        $tmp_filename = '/tmp/' . time() . "_" . basename($filename);
494
        if (false === copy($filename, $tmp_filename)) {
495
            $res->success    = false;
496
            $res->messages[] = "Unable to create temporary file '{$tmp_filename}'.";
497
498
            return $res;
499
        }
500
501
        // Принудительно устанавливаем расширение файла в wav.
502
        $n_filename     = Util::trimExtensionForFile($filename) . ".wav";
503
        $n_filename_mp3 = Util::trimExtensionForFile($filename) . ".mp3";
504
        // Конвертируем файл.
505
        $tmp_filename = escapeshellcmd($tmp_filename);
506
        $n_filename   = escapeshellcmd($n_filename);
507
        $soxPath      = Util::which('sox');
508
        Processes::mwExec("{$soxPath} -v 0.99 -G '{$tmp_filename}' -c 1 -r 8000 -b 16 '{$n_filename}'", $out);
509
        $result_str = implode('', $out);
510
511
        $lamePath = Util::which('lame');
512
        Processes::mwExec("{$lamePath} -b 32 --silent '{$n_filename}' '{$n_filename_mp3}'", $out);
513
        $result_mp3 = implode('', $out);
514
515
        // Чистим мусор.
516
        unlink($tmp_filename);
517
        if ($result_str !== '' && $result_mp3 !== '') {
518
            // Ошибка выполнения конвертации.
519
            $res->success    = false;
520
            $res->messages[] = $result_str;
521
522
            return $res;
523
        }
524
525
        if (file_exists($filename)
526
            && $filename !== $n_filename
527
            && $filename !== $n_filename_mp3) {
528
            unlink($filename);
529
        }
530
531
        $res->success = true;
532
        $res->data[]  = $n_filename_mp3;
533
534
        return $res;
535
    }
536
537
    /**
538
     * Returns Status of module installation process
539
     *
540
     * @param string $filePath
541
     *
542
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
543
     */
544
    public static function statusOfModuleInstallation(string $filePath): PBXApiResult
545
    {
546
        $res            = new PBXApiResult();
547
        $res->processor = __METHOD__;
548
        $di             = Di::getDefault();
549
        if ($di === null) {
550
            $res->messages[] = 'Dependency injector does not initialized';
551
552
            return $res;
553
        }
554
        $temp_dir            = dirname($filePath);
555
        $progress_file = $temp_dir . '/installation_progress';
556
        $error_file = $temp_dir . '/installation_error';
557
        if (!file_exists($error_file)|| !file_exists($progress_file)){
558
            $res->success                   = false;
559
            $res->data['i_status']          = 'PROGRESS_FILE_NOT_FOUND';
560
            $res->data['i_status_progress'] = '0';
561
        }
562
        elseif (file_get_contents($error_file)!=='') {
563
            $res->success                   = false;
564
            $res->data['i_status']          = 'INSTALLATION_ERROR';
565
            $res->data['i_status_progress'] = '0';
566
            $res->messages[]                = file_get_contents($error_file);
567
        } elseif ('100' === file_get_contents($progress_file)) {
568
            $res->success                   = true;
569
            $res->data['i_status_progress'] = '100';
570
            $res->data['i_status']          = 'INSTALLATION_COMPLETE';
571
        } else {
572
            $res->success                   = true;
573
            $res->data['i_status']          = 'INSTALLATION_IN_PROGRESS';
574
            $res->data['i_status_progress'] = file_get_contents($progress_file);
575
        }
576
577
578
        return $res;
579
    }
580
}