Passed
Push — develop ( f46262...cc5787 )
by Nikolay
07:20 queued 11s
created

FilesManagementProcessor::installModuleFromFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 9
c 1
b 0
f 0
dl 0
loc 13
rs 9.9666
cc 2
nc 2
nop 1
1
<?php
2
/**
3
 * Copyright © MIKO LLC - All Rights Reserved
4
 * Unauthorized copying of this file, via any medium is strictly prohibited
5
 * Proprietary and confidential
6
 * Written by Alexey Portnov, 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
41
            return $res;
42
        }
43
        $upload_id            = $parameters['upload_id'];
44
        $resumableFilename    = $parameters['resumableFilename'];
45
        $resumableIdentifier  = $parameters['resumableIdentifier'];
46
        $resumableChunkNumber = $parameters['resumableChunkNumber'];
47
        $resumableTotalSize   = $parameters['resumableTotalSize'];
48
        $uploadDir            = $di->getShared('config')->path('www.uploadDir');
49
50
        $factory = new StreamFactory();
51
52
        foreach ($parameters['files'] as $file_data) {
53
            $stream = $factory->createStreamFromFile($file_data['file_path'], 'r');
54
            $file   = new UploadedFile(
55
                $stream,
56
                $file_data['file_size'],
57
                $file_data['file_error'],
58
                $file_data['file_name'],
59
                $file_data['file_type']
60
            );
61
62
            if (isset($resumableIdentifier) && trim($resumableIdentifier) !== '') {
63
                $temp_dir         = $uploadDir . '/' . Util::trimExtensionForFile(basename($resumableFilename));
64
                $temp_dst_file    = $uploadDir . '/' . $upload_id . '/' . $upload_id . '_' . basename(
65
                        $resumableFilename
66
                    );
67
                $chunks_dest_file = $temp_dir . '/' . $resumableFilename . '.part' . $resumableChunkNumber;
68
            } else {
69
                $temp_dir         = $uploadDir . '/' . $upload_id;
70
                $temp_dst_file    = $temp_dir . '/' . $upload_id . '_' . basename($file->getClientFilename());
71
                $chunks_dest_file = $temp_dst_file;
72
            }
73
            if ( ! Util::mwMkdir($temp_dir) || ! Util::mwMkdir(dirname($temp_dst_file))) {
74
                Util::sysLogMsg('UploadFile', "Error create dir '$temp_dir'");
75
                $res->success    = false;
76
                $res->messages[] = "Error create dir '{$temp_dir}'";
77
78
                return $res;
79
            }
80
            $file->moveTo($chunks_dest_file);
81
            // if (file_exists($file->))
82
            if ($resumableFilename) {
83
                $res->success = true;
84
                // Передача файлов частями.
85
                $result = Util::createFileFromChunks($temp_dir, $resumableTotalSize);
86
                if ($result === true) {
87
                    $merge_settings = [
88
                        'data'   => [
89
                            'result_file'          => $temp_dst_file,
90
                            'temp_dir'             => $temp_dir,
91
                            'resumableFilename'    => $resumableFilename,
92
                            'resumableTotalChunks' => $resumableChunkNumber,
93
                        ],
94
                        'action' => 'merge',
95
                    ];
96
                    $settings_file  = "{$temp_dir}/merge_settings";
97
                    file_put_contents(
98
                        $settings_file,
99
                        json_encode($merge_settings, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)
100
                    );
101
102
                    // Отправляем задачу на склеивание файла.
103
                    $phpPath               = Util::which('php');
104
                    $workerFilesMergerPath = Util::getFilePathByClassName(WorkerMergeUploadedFile::class);
105
                    Util::mwExecBg("{$phpPath} -f {$workerFilesMergerPath} '{$settings_file}'");
106
                    $res->data['upload_id'] = $upload_id;
107
                    $res->data['filename']  = $temp_dst_file;
108
                    $res->data['d_status']  = 'INPROGRESS';
109
                }
110
            } else {
111
                $res->success = true;
112
                // Передача файла целиком.
113
                $res->data['upload_id'] = $upload_id;
114
                $res->data['filename']  = $temp_dst_file;
115
                $res->data['d_status']  = 'UPLOAD_COMPLETE';
116
                file_put_contents($temp_dir . '/progress', '100');
117
118
                Util::mwExecBg(
119
                    '/sbin/shell_functions.sh killprocesses ' . $temp_dir . ' -TERM 0;rm -rf ' . $temp_dir,
120
                    '/dev/null',
121
                    120
122
                );
123
            }
124
        }
125
126
        return $res;
127
    }
128
129
    /**
130
     * Returns Status of uploading process
131
     *
132
     * @param $postData
133
     *
134
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
135
     */
136
    public static function statusUploadFile($postData): PBXApiResult
137
    {
138
        $res            = new PBXApiResult();
139
        $res->processor = __METHOD__;
140
        $di             = Di::getDefault();
141
        if ($di === null) {
142
            $res->success    = false;
143
            $res->messages[] = 'Dependency injector not initialized';
144
145
            return $res;
146
        }
147
        $uploadDir = $di->getShared('config')->path('www.uploadDir');
148
        if ($postData && isset($postData['id'])) {
149
            $upload_id     = $postData['id'];
150
            $progress_dir  = $uploadDir . '/' . $upload_id;
151
            $progress_file = $progress_dir . '/progress';
152
153
            if (empty($upload_id)) {
154
                $res->success                   = false;
155
                $res->data['d_status_progress'] = '0';
156
                $res->data['d_status']          = 'ID_NOT_SET';
157
            } elseif ( ! file_exists($progress_file) && file_exists($progress_dir)) {
158
                $res->success                   = true;
159
                $res->data['d_status_progress'] = '0';
160
                $res->data['d_status']          = 'INPROGRESS';
161
            } elseif ( ! file_exists($progress_dir)) {
162
                $res->success                   = false;
163
                $res->data['d_status_progress'] = '0';
164
                $res->data['d_status']          = 'NOT_FOUND';
165
            } elseif ('100' === file_get_contents($progress_file)) {
166
                $res->success                   = true;
167
                $res->data['d_status_progress'] = '100';
168
                $res->data['d_status']          = 'UPLOAD_COMPLETE';
169
            } else {
170
                $res->success                   = true;
171
                $res->data['d_status_progress'] = file_get_contents($progress_file);
172
            }
173
        }
174
175
        return $res;
176
    }
177
178
    /**
179
     * Конвертация файла в wav 8000.
180
     *
181
     * @param $filename
182
     *
183
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
184
     */
185
    public static function convertAudioFile($filename): PBXApiResult
186
    {
187
        $res            = new PBXApiResult();
188
        $res->processor = __METHOD__;
189
        if ( ! file_exists($filename)) {
190
            $res->success    = false;
191
            $res->messages[] = "File '{$filename}' not found.";
192
193
            return $res;
194
        }
195
        $out          = [];
196
        $tmp_filename = '/tmp/' . time() . "_" . basename($filename);
197
        if (false === copy($filename, $tmp_filename)) {
198
            $res->success    = false;
199
            $res->messages[] = "Unable to create temporary file '{$tmp_filename}'.";
200
201
            return $res;
202
        }
203
204
        // Принудительно устанавливаем расширение файла в wav.
205
        $n_filename     = Util::trimExtensionForFile($filename) . ".wav";
206
        $n_filename_mp3 = Util::trimExtensionForFile($filename) . ".mp3";
207
        // Конвертируем файл.
208
        $tmp_filename = escapeshellcmd($tmp_filename);
209
        $n_filename   = escapeshellcmd($n_filename);
210
        $soxPath      = Util::which('sox');
211
        Util::mwExec("{$soxPath} -v 0.99 -G '{$tmp_filename}' -c 1 -r 8000 -b 16 '{$n_filename}'", $out);
212
        $result_str = implode('', $out);
213
214
        $lamePath = Util::which('lame');
215
        Util::mwExec("{$lamePath} -b 32 --silent '{$n_filename}' '{$n_filename_mp3}'", $out);
216
        $result_mp3 = implode('', $out);
217
218
        // Чистим мусор.
219
        unlink($tmp_filename);
220
        if ($result_str !== '' && $result_mp3 !== '') {
221
            // Ошибка выполнения конвертации.
222
            $res->success    = false;
223
            $res->messages[] = $result_str;
224
225
            return $res;
226
        }
227
228
        if ($filename !== $n_filename && $filename !== $n_filename_mp3) {
229
            @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

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