Passed
Push — develop ( 0a248f...2e4521 )
by Nikolay
05:19
created

FilesManagementProcessor::uninstallModule()   A

Complexity

Conditions 5
Paths 28

Size

Total Lines 45
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 31
dl 0
loc 45
rs 9.1128
c 1
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, 8 2020
7
 */
8
9
namespace MikoPBX\PBXCoreREST\Lib;
10
11
use MikoPBX\Common\Models\CustomFiles;
12
use MikoPBX\Core\System\System;
13
use MikoPBX\Core\System\Util;
14
use MikoPBX\Modules\PbxExtensionUtils;
15
use MikoPBX\Modules\Setup\PbxExtensionSetupFailure;
16
use MikoPBX\PBXCoreREST\Workers\WorkerDownloader;
17
use MikoPBX\PBXCoreREST\Workers\WorkerMergeUploadedFile;
18
use Phalcon\Di;
19
use Phalcon\Di\Injectable;
20
use Phalcon\Http\Message\StreamFactory;
21
use Phalcon\Http\Message\UploadedFile;
22
23
class FilesManagementProcessor extends Injectable
24
{
25
    /**
26
     * Process resumable upload files
27
     *
28
     * @param $parameters
29
     *
30
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
31
     */
32
    public static function uploadResumable($parameters): PBXApiResult
33
    {
34
        $res = new PBXApiResult();
35
        $res->processor = __METHOD__;
36
        $di             = Di::getDefault();
37
        if ($di === null) {
38
            $res->success = false;
39
            $res->messages[]='Dependency injector not initialized';
40
            return $res;
41
        }
42
        $upload_id            = $parameters['upload_id'];
43
        $resumableFilename    = $parameters['resumableFilename'];
44
        $resumableIdentifier  = $parameters['resumableIdentifier'];
45
        $resumableChunkNumber = $parameters['resumableChunkNumber'];
46
        $resumableTotalSize   = $parameters['resumableTotalSize'];
47
        $uploadDir           = $di->getShared('config')->path('www.uploadDir');
48
49
        $factory = new StreamFactory();
50
51
        foreach ($parameters['files'] as $file_data) {
52
            $stream = $factory->createStreamFromFile($file_data['file_path'], 'r');
53
            $file   = new UploadedFile(
54
                $stream,
55
                $file_data['file_size'],
56
                $file_data['file_error'],
57
                $file_data['file_name'],
58
                $file_data['file_type']
59
            );
60
61
            if (isset($resumableIdentifier) && trim($resumableIdentifier) !== '') {
62
                $temp_dir         = $uploadDir . '/' . Util::trimExtensionForFile(basename($resumableFilename));
63
                $temp_dst_file    = $uploadDir . '/' . $upload_id . '/' . $upload_id . '_' . basename(
64
                        $resumableFilename
65
                    );
66
                $chunks_dest_file = $temp_dir . '/' . $resumableFilename . '.part' . $resumableChunkNumber;
67
            } else {
68
                $temp_dir         = $uploadDir . '/' . $upload_id;
69
                $temp_dst_file    = $temp_dir . '/' . $upload_id . '_' . basename($file->getClientFilename());
70
                $chunks_dest_file = $temp_dst_file;
71
            }
72
            if ( ! Util::mwMkdir($temp_dir) || ! Util::mwMkdir(dirname($temp_dst_file))) {
73
                Util::sysLogMsg('UploadFile', "Error create dir '$temp_dir'");
74
                $res->success = false;
75
                $res->messages[] ="Error create dir '{$temp_dir}'";
76
                return $res;
77
            }
78
            $file->moveTo($chunks_dest_file);
79
            // if (file_exists($file->))
80
            if ($resumableFilename) {
81
                $res->success = true;
82
                // Передача файлов частями.
83
                $result = Util::createFileFromChunks($temp_dir, $resumableTotalSize);
84
                if ($result === true) {
85
                    $merge_settings = [
86
                        'data'   => [
87
                            'result_file'          => $temp_dst_file,
88
                            'temp_dir'             => $temp_dir,
89
                            'resumableFilename'    => $resumableFilename,
90
                            'resumableTotalChunks' => $resumableChunkNumber,
91
                        ],
92
                        'action' => 'merge',
93
                    ];
94
                    $settings_file  = "{$temp_dir}/merge_settings";
95
                    file_put_contents(
96
                        $settings_file,
97
                        json_encode($merge_settings, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)
98
                    );
99
100
                    // Отправляем задачу на склеивание файла.
101
                    $phpPath               = Util::which('php');
102
                    $workerFilesMergerPath = Util::getFilePathByClassName(WorkerMergeUploadedFile::class);
103
                    Util::mwExecBg("{$phpPath} -f {$workerFilesMergerPath} '{$settings_file}'");
104
                    $res->data['upload_id'] = $upload_id;
105
                    $res->data['filename']  = $temp_dst_file;
106
                    $res->data['d_status']  = 'INPROGRESS';
107
                }
108
            } else {
109
                $res->success = true;
110
                // Передача файла целиком.
111
                $res->data['upload_id'] = $upload_id;
112
                $res->data['filename']  = $temp_dst_file;
113
                $res->data['d_status']  = 'UPLOAD_COMPLETE';
114
                file_put_contents($temp_dir . '/progress', '100');
115
116
                Util::mwExecBg(
117
                    '/sbin/shell_functions.sh killprocesses ' . $temp_dir . ' -TERM 0;rm -rf ' . $temp_dir,
118
                    '/dev/null',
119
                    120
120
                );
121
            }
122
        }
123
124
        return $res;
125
    }
126
127
    /**
128
     * Returns Status of uploading process
129
     *
130
     * @param $postData
131
     *
132
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
133
     */
134
    public static function statusUploadFile($postData): PBXApiResult
135
    {
136
        $res = new PBXApiResult();
137
        $res->processor = __METHOD__;
138
        $di               = Di::getDefault();
139
        if ($di === null) {
140
            $res->success = false;
141
            $res->messages[]='Dependency injector not initialized';
142
            return $res;
143
        }
144
        $uploadDir = $di->getShared('config')->path('www.uploadDir');
145
        if ($postData && isset($postData['id'])) {
146
            $upload_id     = $postData['id'];
147
            $progress_dir  = $uploadDir . '/' . $upload_id;
148
            $progress_file = $progress_dir . '/progress';
149
150
            if (empty($upload_id)) {
151
                $res->success = false;
152
                $res->data['d_status_progress'] = '0';
153
                $res->data['d_status']          = 'ID_NOT_SET';
154
            } elseif ( ! file_exists($progress_file) && file_exists($progress_dir)) {
155
                $res->success = true;
156
                $res->data['d_status_progress'] = '0';
157
                $res->data['d_status']          = 'INPROGRESS';
158
            } elseif ( ! file_exists($progress_dir)) {
159
                $res->success = false;
160
                $res->data['d_status_progress'] = '0';
161
                $res->data['d_status']          = 'NOT_FOUND';
162
            } elseif ('100' === file_get_contents($progress_file)) {
163
                $res->success = true;
164
                $res->data['d_status_progress'] = '100';
165
                $res->data['d_status']          = 'UPLOAD_COMPLETE';
166
            } else {
167
                $res->success = true;
168
                $res->data['d_status_progress'] = file_get_contents($progress_file);
169
            }
170
        }
171
172
        return $res;
173
    }
174
175
    /**
176
     * Конвертация файла в wav 8000.
177
     *
178
     * @param $filename
179
     *
180
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
181
     */
182
    public static function convertAudioFile($filename): PBXApiResult
183
    {
184
        $res = new PBXApiResult();
185
        $res->processor = __METHOD__;
186
        if ( ! file_exists($filename)) {
187
            $res->success = false;
188
            $res->messages[]="File '{$filename}' not found.";
189
            return $res;
190
        }
191
        $out          = [];
192
        $tmp_filename = '/tmp/' . time() . "_" . basename($filename);
193
        if (false === copy($filename, $tmp_filename)) {
194
            $res->success = false;
195
            $res->messages[] = "Unable to create temporary file '{$tmp_filename}'.";
196
            return $res;
197
        }
198
199
        // Принудительно устанавливаем расширение файла в wav.
200
        $n_filename     = Util::trimExtensionForFile($filename) . ".wav";
201
        $n_filename_mp3 = Util::trimExtensionForFile($filename) . ".mp3";
202
        // Конвертируем файл.
203
        $tmp_filename = escapeshellcmd($tmp_filename);
204
        $n_filename   = escapeshellcmd($n_filename);
205
        $soxPath      = Util::which('sox');
206
        Util::mwExec("{$soxPath} -v 0.99 -G '{$tmp_filename}' -c 1 -r 8000 -b 16 '{$n_filename}'", $out);
207
        $result_str = implode('', $out);
208
209
        $lamePath = Util::which('lame');
210
        Util::mwExec("{$lamePath} -b 32 --silent '{$n_filename}' '{$n_filename_mp3}'", $out);
211
        $result_mp3 = implode('', $out);
212
213
        // Чистим мусор.
214
        unlink($tmp_filename);
215
        if ($result_str !== '' && $result_mp3 !== '') {
216
            // Ошибка выполнения конвертации.
217
            $res->success = false;
218
            $res->messages[] = $result_str;
219
            return $res;
220
        }
221
222
        if ($filename !== $n_filename && $filename !== $n_filename_mp3) {
223
            @unlink($filename);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

223
            /** @scrutinizer ignore-unhandled */ @unlink($filename);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
224
        }
225
226
        $res->success = true;
227
        $res->data[]   = $n_filename_mp3;
228
229
        return $res;
230
    }
231
232
    /**
233
     * Unpack ModuleFile and get metadata information
234
     *
235
     * @param $filePath
236
     *
237
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
238
     */
239
    public static function  getMetadataFromModuleFile(string $filePath): PBXApiResult
240
    {
241
        $res = new PBXApiResult();
242
        $res->processor = __METHOD__;
243
244
        if (file_exists($filePath)) {
245
            $sevenZaPath = Util::which('7za');
246
            $grepPath    = Util::which('grep');
247
            $echoPath    = Util::which('echo');
248
            $awkPath     = Util::which('awk');
249
            $cmd = 'f="' . $filePath . '"; p=`' . $sevenZaPath . ' l $f | ' . $grepPath . ' module.json`;if [ "$?" == "0" ]; then ' . $sevenZaPath . ' -so e -y -r $f `' . $echoPath . ' $p |  ' . $awkPath . ' -F" " \'{print $6}\'`; fi';
250
251
            Util::mwExec($cmd, $out);
252
            $settings = json_decode(implode("\n", $out), true);
253
254
            $moduleUniqueID = $settings['moduleUniqueID'] ?? null;
255
            if ( ! $moduleUniqueID) {
256
                $res->messages[] = 'The" moduleUniqueID " in the module file is not described.the json or file does not exist.';
257
                return $res;
258
            }
259
            $res->success = true;
260
            $res->data   = [
261
                'filePath' => $filePath,
262
                'uniqid' => $moduleUniqueID,
263
            ];
264
        }
265
266
        return $res;
267
    }
268
269
    /**
270
     * Считывает содержимое файла, если есть разрешение.
271
     *
272
     * @param $filename
273
     * @param $needOriginal
274
     *
275
     * @return PBXApiResult
276
     */
277
    public static function fileReadContent($filename, $needOriginal = true): PBXApiResult
278
    {
279
        $res = new PBXApiResult();
280
        $res->processor = __METHOD__;
281
        $customFile    = CustomFiles::findFirst("filepath = '{$filename}'");
282
        if ($customFile !== null) {
283
            $filename_orgn = "{$filename}.orgn";
284
            if ($needOriginal && file_exists($filename_orgn)) {
285
                $filename = $filename_orgn;
286
            }
287
            $res->success = true;
288
            $res->data[]   = rawurlencode(file_get_contents($filename));
289
        } else {
290
            $res->success = false;
291
            $res->messages[] = 'No access to the file '.$filename;
292
        }
293
294
        return $res;
295
    }
296
297
    /**
298
     * Получения данных из лог файла с возможностью фильтра и ограничения выборки.
299
     * @param  string $filename
300
     * @param  string $filter
301
     * @param  int    $lines
302
     * @return PBXApiResult
303
     */
304
    public static function getLogFromFile($filename = 'messages', $filter = '', $lines = 500): PBXApiResult
305
    {
306
        $res = new PBXApiResult();
307
        if(!file_exists($filename)){
308
            $filename = System::getLogDir() .'/'. $filename;
309
        }
310
        if(!file_exists($filename)){
311
            $res->success    = false;
312
            $res->messages[] = 'No access to the file '.$filename;
313
        }else{
314
            $res->success    = true;
315
            $cat  = Util::which('cat');
316
            $grep = Util::which('grep');
317
            $tail = Util::which('tail');
318
319
            $di         = Di::getDefault();
320
            $dirsConfig = $di->getShared('config');
321
            $filenameTmp   = $dirsConfig->path('core.tempDir') . '/'.__FUNCTION__.'_'.time().'.log';
322
            $cmd = "{$cat} {$filename} | {$grep} ".escapeshellarg($filter)." | $tail -n ". escapeshellarg($lines). "> $filenameTmp";
323
            Util::mwExec("$cmd; chown www:www $filenameTmp");
324
            $res->data[] = $filenameTmp;
325
        }
326
        return $res;
327
    }
328
329
    /**
330
     * Download IMG from MikoPBX repository
331
     *
332
     * @param $data
333
     *
334
     * @return PBXApiResult
335
     */
336
    public static function downloadNewFirmware($data): PBXApiResult
337
    {
338
        $di     = Di::getDefault();
339
        if ($di !== null){
340
            $tempDir = $di->getConfig()->path('www.uploadDir');
341
        } else {
342
            $tempDir = '/tmp';
343
        }
344
        $rmPath = Util::which('rm');
345
        $module  = 'NewFirmware';
346
        if ( ! file_exists($tempDir . "/{$module}")) {
347
            Util::mwMkdir($tempDir . "/{$module}");
348
        } else {
349
            // Чистим файлы, загруженные онлайн.
350
            Util::mwExec("{$rmPath} -rf {$tempDir}/{$module}/* ");
351
        }
352
        if (file_exists("{$tempDir}/update.img")) {
353
            // Чистим вручную загруженный файл.
354
            Util::mwExec("{$rmPath} -rf {$tempDir}/update.img");
355
        }
356
357
        $download_settings = [
358
            'res_file' => "{$tempDir}/{$module}/update.img",
359
            'url'      => $data['url'],
360
            'module'   => $module,
361
            'md5'      => $data['md5'],
362
            'action'   => $module,
363
        ];
364
365
        $workerDownloaderPath = Util::getFilePathByClassName(WorkerDownloader::class);
366
367
        file_put_contents($tempDir . "/{$module}/progress", '0');
368
        file_put_contents(
369
            $tempDir . "/{$module}/download_settings.json",
370
            json_encode($download_settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
371
        );
372
        $phpPath = Util::which('php');
373
        Util::mwExecBg("{$phpPath} -f {$workerDownloaderPath} " . $tempDir . "/{$module}/download_settings.json");
374
375
        $res = new PBXApiResult();
376
        $res->processor = __METHOD__;
377
        $res->success = true;
378
        $res->data['filename']=$download_settings['res_file'];
379
        $res->data['d_status']='DOWNLOAD_IN_PROGRESS';
380
381
        return $res;
382
    }
383
384
    /**
385
     * Return download Firmware from remote repository progress
386
     *
387
     * @return PBXApiResult
388
     */
389
    public static function firmwareDownloadStatus(): PBXApiResult
390
    {
391
        clearstatcache();
392
        $res = new PBXApiResult();
393
        $res->processor = __METHOD__;
394
        $res->success = true;
395
        $di     = Di::getDefault();
396
        if ($di !== null){
397
            $tempDir = $di->getConfig()->path('www.uploadDir');
398
        } else {
399
            $tempDir = '/tmp';
400
        }
401
        $modulesDir    = $tempDir . '/NewFirmware';
402
        $progress_file = $modulesDir . '/progress';
403
404
        $error = '';
405
        if (file_exists($modulesDir . '/error')) {
406
            $error = trim(file_get_contents($modulesDir . '/error'));
407
        }
408
409
        if ( ! file_exists($progress_file)) {
410
            $res->data['d_status_progress'] = '0';
411
            $res->data['d_status']          = 'NOT_FOUND';
412
        } elseif ('' !== $error) {
413
            $res->data['d_status']          = 'DOWNLOAD_ERROR';
414
            $res->data['d_status_progress'] = file_get_contents($progress_file);
415
            $res->data['d_error']           = $error;
416
        } elseif ('100' === file_get_contents($progress_file)) {
417
            $res->data['d_status_progress'] = '100';
418
            $res->data['d_status']          = 'DOWNLOAD_COMPLETE';
419
            $res->data['filePath'] = "{$tempDir}/NewFirmware/update.img";
420
        } else {
421
            $res->data['d_status_progress'] = file_get_contents($progress_file);
422
            $d_pid                       = Util::getPidOfProcess($tempDir . '/NewFirmware/download_settings.json');
423
            if (empty($d_pid)) {
424
                $res->data['d_status'] = 'DOWNLOAD_ERROR';
425
                $error              = '';
426
                if (file_exists($modulesDir . '/error')) {
427
                    $error = file_get_contents($modulesDir . '/error');
428
                }
429
                $res->data['d_error'] = $error;
430
            } else {
431
                $res->data['d_status'] = 'DOWNLOAD_IN_PROGRESS';
432
            }
433
        }
434
435
        return $res;
436
    }
437
438
    /**
439
     * Start module download in background separate process
440
     *
441
     * @param $module
442
     * @param $url
443
     * @param $md5
444
     *
445
     * @return PBXApiResult
446
     */
447
    public static function moduleStartDownload($module, $url, $md5): PBXApiResult
448
    {
449
        $res = new PBXApiResult();
450
        $res->processor = __METHOD__;
451
        $di     = Di::getDefault();
452
        if ($di !== null){
453
            $tempDir = $di->getConfig()->path('www.uploadDir');
454
        } else {
455
            $tempDir = '/tmp';
456
        }
457
458
        $moduleDirTmp = "{$tempDir}/{$module}";
459
        Util::mwMkdir($moduleDirTmp);
460
461
        $download_settings = [
462
            'res_file' => "$moduleDirTmp/modulefile.zip",
463
            'url'      => $url,
464
            'module'   => $module,
465
            'md5'      => $md5,
466
            'action'   => 'moduleInstall',
467
        ];
468
        if (file_exists("$moduleDirTmp/error")) {
469
            unlink("$moduleDirTmp/error");
470
        }
471
        if (file_exists("$moduleDirTmp/installed")) {
472
            unlink("$moduleDirTmp/installed");
473
        }
474
        file_put_contents("$moduleDirTmp/progress", '0');
475
        file_put_contents(
476
            "$moduleDirTmp/download_settings.json",
477
            json_encode($download_settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
478
        );
479
        $workerDownloaderPath = Util::getFilePathByClassName(WorkerDownloader::class);
480
        $phpPath = Util::which('php');
481
        Util::mwExecBg("{$phpPath} -f {$workerDownloaderPath} $moduleDirTmp/download_settings.json");
482
483
        $res->data['uniqid'] = $module;
484
        $res->data['d_status'] = 'DOWNLOAD_IN_PROGRESS';
485
        $res->success = true;
486
        return $res;
487
    }
488
489
    /**
490
     * Returns module download status
491
     *
492
     * @param $moduleUniqueID
493
     *
494
     * @return PBXApiResult
495
     */
496
    public static function moduleDownloadStatus(string $moduleUniqueID): PBXApiResult
497
    {
498
        clearstatcache();
499
        $res = new PBXApiResult();
500
        $res->processor = __METHOD__;
501
        $di     = Di::getDefault();
502
        if ($di !== null){
503
            $tempDir = $di->getConfig()->path('www.uploadDir');
504
        } else {
505
            $tempDir = '/tmp';
506
        }
507
        $moduleDirTmp  = $tempDir . '/' . $moduleUniqueID;
508
        $progress_file = $moduleDirTmp . '/progress';
509
        $error         = '';
510
        if (file_exists($moduleDirTmp . '/error')) {
511
            $error = trim(file_get_contents($moduleDirTmp . '/error'));
512
        }
513
514
        // Ожидание запуска процесса загрузки.
515
        $d_pid = Util::getPidOfProcess("{$moduleDirTmp}/download_settings.json");
516
        if (empty($d_pid)) {
517
            usleep(500000);
518
        }
519
520
        if ( ! file_exists($progress_file)) {
521
            $res->data['d_status_progress'] = '0';
522
            $res->data['d_status']          = 'NOT_FOUND';
523
            $res->success = false;
524
        } elseif ('' !== $error) {
525
            $res->data['d_status']          = 'DOWNLOAD_ERROR';
526
            $res->data['d_status_progress'] = file_get_contents($progress_file);
527
            $res->data['d_error']           = $error;
528
            $res->success = false;
529
        } elseif ('100' === file_get_contents($progress_file)) {
530
            $res->data['d_status_progress'] = '100';
531
            $res->data['d_status']          = 'DOWNLOAD_COMPLETE';
532
            $res->data['filePath'] =  "$moduleDirTmp/modulefile.zip";
533
            $res->success = true;
534
        } else {
535
            $res->data['d_status_progress'] = file_get_contents($progress_file);
536
            $d_pid                       = Util::getPidOfProcess($moduleDirTmp . '/download_settings.json');
537
            if (empty($d_pid)) {
538
                $res->data['d_status'] = 'DOWNLOAD_ERROR';
539
                $error              = '';
540
                if (file_exists($moduleDirTmp . '/error')) {
541
                    $error = file_get_contents($moduleDirTmp . '/error');
542
                }
543
                $res->messages[] = $error;
544
                $res->success = false;
545
            } else {
546
                $res->data['d_status'] = 'DOWNLOAD_IN_PROGRESS';
547
                $res->success = true;
548
            }
549
        }
550
        return $res;
551
    }
552
553
    /**
554
     * Delete file from disk by filepath
555
     *
556
     * @param $filePath
557
     *
558
     * @return PBXApiResult
559
     */
560
    public static function removeAudioFile($filePath): PBXApiResult
561
    {
562
        $res = new PBXApiResult();
563
        $res->processor = __METHOD__;
564
        $extension = Util::getExtensionOfFile($filePath);
565
        if ( ! in_array($extension, ['mp3', 'wav', 'alaw'])) {
566
            $res->success = false;
567
            $res->messages[] = "It is forbidden to remove the file type $extension.";
568
            return $res;
569
        }
570
571
        if ( ! file_exists($filePath)) {
572
            $res->success = true;
573
            $res->data['message']="File '{$filePath}' already deleted";
574
            return $res;
575
        }
576
577
        $out = [];
578
579
        $arrDeletedFiles = [
580
            escapeshellarg(Util::trimExtensionForFile($filePath) . ".wav"),
581
            escapeshellarg(Util::trimExtensionForFile($filePath) . ".mp3"),
582
            escapeshellarg(Util::trimExtensionForFile($filePath) . ".alaw"),
583
        ];
584
585
        $rmPath = Util::which('rm');
586
        Util::mwExec("{$rmPath} -rf " . implode(' ', $arrDeletedFiles), $out);
587
        if (file_exists($filePath)) {
588
            $res->success = false;
589
            $res->messages = $out;
590
        } else {
591
            $res->success = true;
592
        }
593
594
        return $res;
595
    }
596
597
    /**
598
     * Install new additional extension module
599
     *
600
     * @param $filePath
601
     *
602
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
603
     *
604
     */
605
    public static function installModuleFromFile($filePath): PBXApiResult
606
    {
607
        $res = new PBXApiResult();
608
        $res->processor = __METHOD__;
609
        $moduleMetadata = FilesManagementProcessor::getMetadataFromModuleFile($filePath);
610
        if (!$moduleMetadata->success) {
611
            return $moduleMetadata;
612
        } else {
613
            $moduleUniqueID = $moduleMetadata->data['uniqid'];
614
            $res = self::installModule($filePath, $moduleUniqueID);
615
        }
616
        return $res;
617
    }
618
619
    /**
620
     * Install module from file
621
     *
622
     * @param string $filePath
623
     *
624
     * @param string $moduleUniqueID
625
     *
626
     * @return PBXApiResult
627
     */
628
    public static function installModule(string $filePath, string $moduleUniqueID):PBXApiResult
629
    {
630
        $res = new PBXApiResult();
631
        $res->processor = __METHOD__;
632
        $res->success = true;
633
        $currentModuleDir = PbxExtensionUtils::getModuleDir($moduleUniqueID);
634
        $needBackup       = is_dir($currentModuleDir);
635
636
        if ($needBackup){
637
            self::uninstallModule($moduleUniqueID, true);
638
        }
639
640
        $semZaPath = Util::which('7za');
641
        Util::mwExec("{$semZaPath} e -spf -aoa -o{$currentModuleDir} {$filePath}");
642
        Util::addRegularWWWRights($currentModuleDir);
643
644
        $pbxExtensionSetupClass       = "\\Modules\\{$moduleUniqueID}\\Setup\\PbxExtensionSetup";
645
        if (class_exists($pbxExtensionSetupClass)
646
            && method_exists($pbxExtensionSetupClass, 'installModule'))
647
        {
648
            $setup = new $pbxExtensionSetupClass($moduleUniqueID);
649
            if ( ! $setup->installModule()) {
650
                $res->success = false;
651
                $res->messages[] = $setup->getMessages();
652
            }
653
        } else {
654
            $res->success = false;
655
            $res->messages[]  = "Install error: the class {$pbxExtensionSetupClass} not exists";
656
        }
657
658
        if ($res->success) {
659
            $res->data['needRestartWorkers'] = true;
660
        }
661
662
        return $res;
663
    }
664
665
    /**
666
     * Uninstall module
667
     * @param string $moduleUniqueID
668
     *
669
     * @param bool   $keepSettings
670
     *
671
     * @return PBXApiResult
672
     */
673
    public static function uninstallModule(string $moduleUniqueID, bool $keepSettings):PBXApiResult
674
    {
675
        $res = new PBXApiResult();
676
        $res->processor = __METHOD__;
677
        $currentModuleDir = PbxExtensionUtils::getModuleDir($moduleUniqueID);
678
        // Kill all module processes
679
        if (is_dir("{$currentModuleDir}/bin")) {
680
            $busyboxPath = Util::which('busybox');
681
            $killPath    = Util::which('kill');
682
            $lsofPath    = Util::which('lsof');
683
            $grepPath    = Util::which('grep');
684
            $awkPath     = Util::which('awk');
685
            $uniqPath    = Util::which('uniq');
686
            Util::mwExec(
687
                "{$busyboxPath} {$killPath} -9 $({$lsofPath} {$currentModuleDir}/bin/* |  {$busyboxPath} {$grepPath} -v COMMAND | {$busyboxPath} {$awkPath}  '{ print $2}' | {$busyboxPath} {$uniqPath})"
688
            );
689
        }
690
        // Uninstall module with keep settings and backup db
691
        $moduleClass = "\\Modules\\{$moduleUniqueID}\\Setup\\PbxExtensionSetup";
692
693
        try {
694
            if (class_exists($moduleClass)
695
                && method_exists($moduleClass, 'uninstallModule')) {
696
                $setup = new $moduleClass($moduleUniqueID);
697
            } else {
698
                // Заглушка которая позволяет удалить модуль из базы данных, которого нет на диске
699
                $moduleClass = PbxExtensionSetupFailure::class;
700
                $setup       = new $moduleClass($moduleUniqueID);
701
            }
702
            $setup->uninstallModule($keepSettings);
703
        }  finally {
704
            if (is_dir($currentModuleDir)) {
705
                // Broken or very old module. Force uninstall.
706
                $rmPath = Util::which('rm');
707
                Util::mwExec("{$rmPath} -rf {$currentModuleDir}");
708
709
                $moduleClass = PbxExtensionSetupFailure::class;
710
                $setup       = new $moduleClass($moduleUniqueID);
711
                $setup->unregisterModule();
712
            }
713
714
        }
715
        $res->success = true;
716
        $res->data['needRestartWorkers'] = true;
717
        return $res;
718
    }
719
}