Passed
Pull Request — master (#20)
by Nikolay
04:51
created

SystemManagementProcessor::updateCoreLanguageAction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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