Test Setup Failed
Push — develop ( 09a6a8...5231a1 )
by Nikolay
10:01 queued 11s
created

FilesManagementProcessor::convertAudioFile()   B

Complexity

Conditions 8
Paths 5

Size

Total Lines 53
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 34
dl 0
loc 53
rs 8.1315
c 1
b 0
f 0
cc 8
nc 5
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Util;
13
use MikoPBX\Modules\PbxExtensionUtils;
14
use MikoPBX\Modules\Setup\PbxExtensionSetupFailure;
15
use MikoPBX\PBXCoreREST\Workers\WorkerDownloader;
16
use MikoPBX\PBXCoreREST\Workers\WorkerMergeUploadedFile;
17
use Phalcon\Di;
18
use Phalcon\Di\Injectable;
19
use Phalcon\Http\Message\StreamFactory;
20
use Phalcon\Http\Message\UploadedFile;
21
22
class FilesManagementProcessor extends Injectable
23
{
24
    /**
25
     * Processes file upload requests
26
     *
27
     * @param array $request
28
     *
29
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
30
     */
31
    public static function callBack(array $request): PBXApiResult
32
    {
33
        $action   = $request['action'];
34
        $postData = $request['data'];
35
        switch ($action) {
36
            case 'uploadResumable':
37
                $res = FilesManagementProcessor::uploadResumable($postData);
38
                break;
39
            case 'status':
40
                $res = FilesManagementProcessor::statusUploadFile($request['data']);
41
                break;
42
            default:
43
                $res             = new PBXApiResult();
44
                $res->processor  = __METHOD__;
45
                $res->messages[] = "Unknown action - {$action} in uploadCallBack";
46
        }
47
48
        $res->function = $action;
49
50
        return $res;
51
    }
52
53
    /**
54
     * Process resumable upload files
55
     *
56
     * @param array $parameters
57
     *
58
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
59
     */
60
    public static function uploadResumable(array $parameters): PBXApiResult
61
    {
62
        $res            = new PBXApiResult();
63
        $res->processor = __METHOD__;
64
        $di             = Di::getDefault();
65
        if ($di === null) {
66
            $res->success    = false;
67
            $res->messages[] = 'Dependency injector does not initialized';
68
            return $res;
69
        }
70
        $parameters['uploadDir'] = $di->getShared('config')->path('www.uploadDir');
71
        $parameters['tempDir'] = "{$parameters['uploadDir']}/{$parameters['resumableIdentifier']}";
72
        if ( ! Util::mwMkdir($parameters['tempDir'])) {
73
            $res->messages[] = 'Temp dir does not exist ' . $parameters['tempDir'];
74
            return $res;
75
        }
76
77
        $fileName                           = pathinfo($parameters['resumableFilename'], PATHINFO_BASENAME);
78
        $parameters['fullUploadedFileName'] = "{$parameters['tempDir']}/{$fileName}";
79
80
        // Delete old progress and result file
81
        $oldMergeProgressFile = "{$parameters['tempDir']}/merging_progress";
82
        if (file_exists($oldMergeProgressFile)){
83
            unlink($oldMergeProgressFile);
84
        }
85
        if (file_exists($parameters['fullUploadedFileName'])){
86
            unlink($parameters['fullUploadedFileName']);
87
        }
88
89
        foreach ($parameters['files'] as $file_data) {
90
            if (!self::moveUploadedPartToSeparateDir($parameters, $file_data)){
91
                $res->messages[] = 'Does not found any uploaded chunks on with path '.$file_data['file_path'];
92
                return $res;
93
            }
94
            $res->success           = true;
95
            $res->data['upload_id'] = $parameters['resumableIdentifier'];
96
            $res->data['filename']  = $parameters['fullUploadedFileName'];
97
98
            if (self::tryToMergeChunksIfAllPartsUploaded($parameters)) {
99
                $res->data['d_status'] = 'MERGING';
100
            } else {
101
                $res->data['d_status'] = 'WAITING_FOR_NEXT_PART';
102
            }
103
        }
104
105
        return $res;
106
    }
107
108
    /**
109
     * Moves uploaded file part to separate directory with "upload_id" name on the system uploadDir folder.
110
     *
111
     * @param array $parameters data from of resumable request
112
     * @param array $file_data  data from uploaded file part
113
     *
114
     * @return bool
115
     */
116
    private static function moveUploadedPartToSeparateDir(array $parameters, array $file_data):bool
117
    {
118
        if ( ! file_exists($file_data['file_path'])) {
119
            return false;
120
        }
121
        $factory          = new StreamFactory();
122
        $stream           = $factory->createStreamFromFile($file_data['file_path'], 'r');
123
        $file             = new UploadedFile(
124
            $stream,
125
            $file_data['file_size'],
126
            $file_data['file_error'],
127
            $file_data['file_name'],
128
            $file_data['file_type']
129
        );
130
        $chunks_dest_file = "{$parameters['tempDir']}/{$parameters['resumableFilename']}.part{$parameters['resumableChunkNumber']}";
131
        if (file_exists($chunks_dest_file)){
132
            $rm = Util::which('rm');
133
            Util::mwExec("{$rm} -f {$chunks_dest_file}");
134
        }
135
        $file->moveTo($chunks_dest_file);
136
        return true;
137
    }
138
139
    /**
140
     * If the size of all the chunks on the server is equal to the size of the file uploaded starts a merge process.
141
     *
142
     * @param array $parameters
143
     *
144
     * @return bool
145
     */
146
    private static function tryToMergeChunksIfAllPartsUploaded(array $parameters): bool
147
    {
148
        $totalFilesOnServerSize = 0;
149
        foreach (scandir($parameters['tempDir']) as $file) {
150
            $totalFilesOnServerSize += filesize($parameters['tempDir'] . '/' . $file);
151
        }
152
153
        if ($totalFilesOnServerSize >= $parameters['resumableTotalSize']) {
154
            // Parts upload complete
155
            $merge_settings = [
156
                    'fullUploadedFileName' => $parameters['fullUploadedFileName'],
157
                    'tempDir'              => $parameters['tempDir'],
158
                    'resumableFilename'    => $parameters['resumableFilename'],
159
                    'resumableTotalSize'   => $parameters['resumableTotalSize'],
160
                    'resumableTotalChunks' => $parameters['resumableTotalChunks'],
161
            ];
162
            $settings_file  = "{$parameters['tempDir']}/merge_settings";
163
            file_put_contents(
164
                $settings_file,
165
                json_encode($merge_settings, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)
166
            );
167
168
            // We will start the background process to merge parts into one file
169
            $phpPath               = Util::which('php');
170
            $workerFilesMergerPath = Util::getFilePathByClassName(WorkerMergeUploadedFile::class);
171
            Util::mwExecBg("{$phpPath} -f {$workerFilesMergerPath} '{$settings_file}'");
172
            return true;
173
        }
174
        return false;
175
    }
176
177
    /**
178
     * Returns Status of uploading process
179
     *
180
     * @param array $postData
181
     *
182
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
183
     */
184
    public static function statusUploadFile(array $postData): PBXApiResult
185
    {
186
        $res            = new PBXApiResult();
187
        $res->processor = __METHOD__;
188
        $di             = Di::getDefault();
189
        if ($di === null) {
190
            $res->messages[] = 'Dependency injector does not initialized';
191
192
            return $res;
193
        }
194
        $uploadDir = $di->getShared('config')->path('www.uploadDir');
195
196
        $upload_id     = $postData['id'] ?? null;
197
        $progress_dir  = $uploadDir . '/' . $upload_id;
198
        $progress_file = $progress_dir . '/merging_progress';
199
        if (empty($upload_id)) {
200
            $res->success                   = false;
201
            $res->data['d_status_progress'] = '0';
202
            $res->data['d_status']          = 'ID_NOT_SET';
203
            $res->messages[]                = 'Upload ID does not set';
204
        } elseif ( ! file_exists($progress_file) && file_exists($progress_dir)) {
205
            $res->success                   = true;
206
            $res->data['d_status_progress'] = '0';
207
            $res->data['d_status']          = 'INPROGRESS';
208
        } elseif ( ! file_exists($progress_dir)) {
209
            $res->success                   = false;
210
            $res->data['d_status_progress'] = '0';
211
            $res->data['d_status']          = 'NOT_FOUND';
212
            $res->messages[]                = 'Does not found anything with path: ' . $progress_dir;
213
        } elseif ('100' === file_get_contents($progress_file)) {
214
            $res->success                   = true;
215
            $res->data['d_status_progress'] = '100';
216
            $res->data['d_status']          = 'UPLOAD_COMPLETE';
217
        } else {
218
            $res->success                   = true;
219
            $res->data['d_status_progress'] = file_get_contents($progress_file);
220
        }
221
222
223
        return $res;
224
    }
225
226
    /**
227
     * Конвертация файла в wav 8000.
228
     *
229
     * @param $filename
230
     *
231
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
232
     */
233
    public static function convertAudioFile($filename): PBXApiResult
234
    {
235
        $res            = new PBXApiResult();
236
        $res->processor = __METHOD__;
237
        if ( ! file_exists($filename)) {
238
            $res->success    = false;
239
            $res->messages[] = "File '{$filename}' not found.";
240
241
            return $res;
242
        }
243
        $out          = [];
244
        $tmp_filename = '/tmp/' . time() . "_" . basename($filename);
245
        if (false === copy($filename, $tmp_filename)) {
246
            $res->success    = false;
247
            $res->messages[] = "Unable to create temporary file '{$tmp_filename}'.";
248
249
            return $res;
250
        }
251
252
        // Принудительно устанавливаем расширение файла в wav.
253
        $n_filename     = Util::trimExtensionForFile($filename) . ".wav";
254
        $n_filename_mp3 = Util::trimExtensionForFile($filename) . ".mp3";
255
        // Конвертируем файл.
256
        $tmp_filename = escapeshellcmd($tmp_filename);
257
        $n_filename   = escapeshellcmd($n_filename);
258
        $soxPath      = Util::which('sox');
259
        Util::mwExec("{$soxPath} -v 0.99 -G '{$tmp_filename}' -c 1 -r 8000 -b 16 '{$n_filename}'", $out);
260
        $result_str = implode('', $out);
261
262
        $lamePath = Util::which('lame');
263
        Util::mwExec("{$lamePath} -b 32 --silent '{$n_filename}' '{$n_filename_mp3}'", $out);
264
        $result_mp3 = implode('', $out);
265
266
        // Чистим мусор.
267
        unlink($tmp_filename);
268
        if ($result_str !== '' && $result_mp3 !== '') {
269
            // Ошибка выполнения конвертации.
270
            $res->success    = false;
271
            $res->messages[] = $result_str;
272
273
            return $res;
274
        }
275
276
        if (file_exists($filename)
277
            && $filename !== $n_filename
278
            && $filename !== $n_filename_mp3) {
279
            unlink($filename);
280
        }
281
282
        $res->success = true;
283
        $res->data[]  = $n_filename_mp3;
284
285
        return $res;
286
    }
287
288
    /**
289
     * Считывает содержимое файла, если есть разрешение.
290
     *
291
     * @param $filename
292
     * @param $needOriginal
293
     *
294
     * @return PBXApiResult
295
     */
296
    public static function fileReadContent($filename, $needOriginal = true): PBXApiResult
297
    {
298
        $res            = new PBXApiResult();
299
        $res->processor = __METHOD__;
300
        $customFile     = CustomFiles::findFirst("filepath = '{$filename}'");
301
        if ($customFile !== null) {
302
            $filename_orgn = "{$filename}.orgn";
303
            if ($needOriginal && file_exists($filename_orgn)) {
304
                $filename = $filename_orgn;
305
            }
306
            $res->success = true;
307
            $cat          = Util::which('cat');
308
            $di           = Di::getDefault();
309
            $dirsConfig   = $di->getShared('config');
310
            $filenameTmp  = $dirsConfig->path('www.downloadCacheDir') . '/' . __FUNCTION__ . '_' . time() . '.conf';
311
            $cmd          = "{$cat} {$filename} > {$filenameTmp}";
312
            Util::mwExec("{$cmd}; chown www:www {$filenameTmp}");
313
            $res->data['filename'] = $filenameTmp;
314
        } else {
315
            $res->success    = false;
316
            $res->messages[] = 'No access to the file ' . $filename;
317
        }
318
319
        return $res;
320
    }
321
322
    /**
323
     * Download IMG from MikoPBX repository
324
     *
325
     * @param $data
326
     *
327
     * @return PBXApiResult
328
     */
329
    public static function downloadNewFirmware($data): PBXApiResult
330
    {
331
        $di = Di::getDefault();
332
        if ($di !== null) {
333
            $tempDir = $di->getConfig()->path('www.uploadDir');
334
        } else {
335
            $tempDir = '/tmp';
336
        }
337
        $rmPath = Util::which('rm');
338
        $module = 'NewFirmware';
339
        if ( ! file_exists($tempDir . "/{$module}")) {
340
            Util::mwMkdir($tempDir . "/{$module}");
341
        } else {
342
            // Чистим файлы, загруженные онлайн.
343
            Util::mwExec("{$rmPath} -rf {$tempDir}/{$module}/* ");
344
        }
345
        if (file_exists("{$tempDir}/update.img")) {
346
            // Чистим вручную загруженный файл.
347
            Util::mwExec("{$rmPath} -rf {$tempDir}/update.img");
348
        }
349
350
        $download_settings = [
351
            'res_file' => "{$tempDir}/{$module}/update.img",
352
            'url'      => $data['url'],
353
            'module'   => $module,
354
            'md5'      => $data['md5'],
355
            'action'   => $module,
356
        ];
357
358
        $workerDownloaderPath = Util::getFilePathByClassName(WorkerDownloader::class);
359
360
        file_put_contents($tempDir . "/{$module}/progress", '0');
361
        file_put_contents(
362
            $tempDir . "/{$module}/download_settings.json",
363
            json_encode($download_settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
364
        );
365
        $phpPath = Util::which('php');
366
        Util::mwExecBg("{$phpPath} -f {$workerDownloaderPath} " . $tempDir . "/{$module}/download_settings.json");
367
368
        $res                   = new PBXApiResult();
369
        $res->processor        = __METHOD__;
370
        $res->success          = true;
371
        $res->data['filename'] = $download_settings['res_file'];
372
        $res->data['d_status'] = 'DOWNLOAD_IN_PROGRESS';
373
374
        return $res;
375
    }
376
377
    /**
378
     * Return download Firmware from remote repository progress
379
     *
380
     * @return PBXApiResult
381
     */
382
    public static function firmwareDownloadStatus(): PBXApiResult
383
    {
384
        clearstatcache();
385
        $res            = new PBXApiResult();
386
        $res->processor = __METHOD__;
387
        $res->success   = true;
388
        $di             = Di::getDefault();
389
        if ($di !== null) {
390
            $tempDir = $di->getConfig()->path('www.uploadDir');
391
        } else {
392
            $tempDir = '/tmp';
393
        }
394
        $modulesDir    = $tempDir . '/NewFirmware';
395
        $progress_file = $modulesDir . '/progress';
396
397
        $error = '';
398
        if (file_exists($modulesDir . '/error')) {
399
            $error = trim(file_get_contents($modulesDir . '/error'));
400
        }
401
402
        if ( ! file_exists($progress_file)) {
403
            $res->data['d_status_progress'] = '0';
404
            $res->data['d_status']          = 'NOT_FOUND';
405
        } elseif ('' !== $error) {
406
            $res->data['d_status']          = 'DOWNLOAD_ERROR';
407
            $res->data['d_status_progress'] = file_get_contents($progress_file);
408
            $res->data['d_error']           = $error;
409
        } elseif ('100' === file_get_contents($progress_file)) {
410
            $res->data['d_status_progress'] = '100';
411
            $res->data['d_status']          = 'DOWNLOAD_COMPLETE';
412
            $res->data['filePath']          = "{$tempDir}/NewFirmware/update.img";
413
        } else {
414
            $res->data['d_status_progress'] = file_get_contents($progress_file);
415
            $d_pid                          = Util::getPidOfProcess($tempDir . '/NewFirmware/download_settings.json');
416
            if (empty($d_pid)) {
417
                $res->data['d_status'] = 'DOWNLOAD_ERROR';
418
                $error                 = '';
419
                if (file_exists($modulesDir . '/error')) {
420
                    $error = file_get_contents($modulesDir . '/error');
421
                }
422
                $res->data['d_error'] = $error;
423
            } else {
424
                $res->data['d_status'] = 'DOWNLOAD_IN_PROGRESS';
425
            }
426
        }
427
428
        return $res;
429
    }
430
431
    /**
432
     * Start module download in background separate process
433
     *
434
     * @param $module
435
     * @param $url
436
     * @param $md5
437
     *
438
     * @return PBXApiResult
439
     */
440
    public static function moduleStartDownload($module, $url, $md5): PBXApiResult
441
    {
442
        $res            = new PBXApiResult();
443
        $res->processor = __METHOD__;
444
        $di             = Di::getDefault();
445
        if ($di !== null) {
446
            $tempDir = $di->getConfig()->path('www.uploadDir');
447
        } else {
448
            $tempDir = '/tmp';
449
        }
450
451
        $moduleDirTmp = "{$tempDir}/{$module}";
452
        Util::mwMkdir($moduleDirTmp);
453
454
        $download_settings = [
455
            'res_file' => "$moduleDirTmp/modulefile.zip",
456
            'url'      => $url,
457
            'module'   => $module,
458
            'md5'      => $md5,
459
            'action'   => 'moduleInstall',
460
        ];
461
        if (file_exists("$moduleDirTmp/error")) {
462
            unlink("$moduleDirTmp/error");
463
        }
464
        if (file_exists("$moduleDirTmp/installed")) {
465
            unlink("$moduleDirTmp/installed");
466
        }
467
        file_put_contents("$moduleDirTmp/progress", '0');
468
        file_put_contents(
469
            "$moduleDirTmp/download_settings.json",
470
            json_encode($download_settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
471
        );
472
        $workerDownloaderPath = Util::getFilePathByClassName(WorkerDownloader::class);
473
        $phpPath              = Util::which('php');
474
        Util::mwExecBg("{$phpPath} -f {$workerDownloaderPath} $moduleDirTmp/download_settings.json");
475
476
        $res->data['uniqid']   = $module;
477
        $res->data['d_status'] = 'DOWNLOAD_IN_PROGRESS';
478
        $res->success          = true;
479
480
        return $res;
481
    }
482
483
    /**
484
     * Returns module download status
485
     *
486
     * @param $moduleUniqueID
487
     *
488
     * @return PBXApiResult
489
     */
490
    public static function moduleDownloadStatus(string $moduleUniqueID): PBXApiResult
491
    {
492
        clearstatcache();
493
        $res            = new PBXApiResult();
494
        $res->processor = __METHOD__;
495
        $di             = Di::getDefault();
496
        if ($di !== null) {
497
            $tempDir = $di->getConfig()->path('www.uploadDir');
498
        } else {
499
            $tempDir = '/tmp';
500
        }
501
        $moduleDirTmp  = $tempDir . '/' . $moduleUniqueID;
502
        $progress_file = $moduleDirTmp . '/progress';
503
        $error         = '';
504
        if (file_exists($moduleDirTmp . '/error')) {
505
            $error = trim(file_get_contents($moduleDirTmp . '/error'));
506
        }
507
508
        // Ожидание запуска процесса загрузки.
509
        $d_pid = Util::getPidOfProcess("{$moduleDirTmp}/download_settings.json");
510
        if (empty($d_pid)) {
511
            usleep(500000);
512
        }
513
514
        if ( ! file_exists($progress_file)) {
515
            $res->data['d_status_progress'] = '0';
516
            $res->data['d_status']          = 'NOT_FOUND';
517
            $res->success                   = false;
518
        } elseif ('' !== $error) {
519
            $res->data['d_status']          = 'DOWNLOAD_ERROR';
520
            $res->data['d_status_progress'] = file_get_contents($progress_file);
521
            $res->data['d_error']           = $error;
522
            $res->success                   = false;
523
        } elseif ('100' === file_get_contents($progress_file)) {
524
            $res->data['d_status_progress'] = '100';
525
            $res->data['d_status']          = 'DOWNLOAD_COMPLETE';
526
            $res->data['filePath']          = "$moduleDirTmp/modulefile.zip";
527
            $res->success                   = true;
528
        } else {
529
            $res->data['d_status_progress'] = file_get_contents($progress_file);
530
            $d_pid                          = Util::getPidOfProcess($moduleDirTmp . '/download_settings.json');
531
            if (empty($d_pid)) {
532
                $res->data['d_status'] = 'DOWNLOAD_ERROR';
533
                $error                 = '';
534
                if (file_exists($moduleDirTmp . '/error')) {
535
                    $error = file_get_contents($moduleDirTmp . '/error');
536
                }
537
                $res->messages[] = $error;
538
                $res->success    = false;
539
            } else {
540
                $res->data['d_status'] = 'DOWNLOAD_IN_PROGRESS';
541
                $res->success          = true;
542
            }
543
        }
544
545
        return $res;
546
    }
547
548
    /**
549
     * Delete file from disk by filepath
550
     *
551
     * @param $filePath
552
     *
553
     * @return PBXApiResult
554
     */
555
    public static function removeAudioFile($filePath): PBXApiResult
556
    {
557
        $res            = new PBXApiResult();
558
        $res->processor = __METHOD__;
559
        $extension      = Util::getExtensionOfFile($filePath);
560
        if ( ! in_array($extension, ['mp3', 'wav', 'alaw'])) {
561
            $res->success    = false;
562
            $res->messages[] = "It is forbidden to remove the file type $extension.";
563
564
            return $res;
565
        }
566
567
        if ( ! file_exists($filePath)) {
568
            $res->success         = true;
569
            $res->data['message'] = "File '{$filePath}' already deleted";
570
571
            return $res;
572
        }
573
574
        $out = [];
575
576
        $arrDeletedFiles = [
577
            escapeshellarg(Util::trimExtensionForFile($filePath) . ".wav"),
578
            escapeshellarg(Util::trimExtensionForFile($filePath) . ".mp3"),
579
            escapeshellarg(Util::trimExtensionForFile($filePath) . ".alaw"),
580
        ];
581
582
        $rmPath = Util::which('rm');
583
        Util::mwExec("{$rmPath} -rf " . implode(' ', $arrDeletedFiles), $out);
584
        if (file_exists($filePath)) {
585
            $res->success  = false;
586
            $res->messages = $out;
587
        } else {
588
            $res->success = true;
589
        }
590
591
        return $res;
592
    }
593
594
    /**
595
     * Install new additional extension module
596
     *
597
     * @param $filePath
598
     *
599
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
600
     *
601
     */
602
    public static function installModuleFromFile($filePath): PBXApiResult
603
    {
604
        $res            = new PBXApiResult();
605
        $res->processor = __METHOD__;
606
        $moduleMetadata = FilesManagementProcessor::getMetadataFromModuleFile($filePath);
607
        if ( ! $moduleMetadata->success) {
608
            return $moduleMetadata;
609
        } else {
610
            $moduleUniqueID = $moduleMetadata->data['uniqid'];
611
            $res            = self::installModule($filePath, $moduleUniqueID);
612
        }
613
614
        return $res;
615
    }
616
617
    /**
618
     * Unpack ModuleFile and get metadata information
619
     *
620
     * @param $filePath
621
     *
622
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
623
     */
624
    public static function getMetadataFromModuleFile(string $filePath): PBXApiResult
625
    {
626
        $res            = new PBXApiResult();
627
        $res->processor = __METHOD__;
628
629
        if (file_exists($filePath)) {
630
            $sevenZaPath = Util::which('7za');
631
            $grepPath    = Util::which('grep');
632
            $echoPath    = Util::which('echo');
633
            $awkPath     = Util::which('awk');
634
            $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';
635
636
            Util::mwExec($cmd, $out);
637
            $settings = json_decode(implode("\n", $out), true);
638
639
            $moduleUniqueID = $settings['moduleUniqueID'] ?? null;
640
            if ( ! $moduleUniqueID) {
641
                $res->messages[] = 'The" moduleUniqueID " in the module file is not described.the json or file does not exist.';
642
643
                return $res;
644
            }
645
            $res->success = true;
646
            $res->data    = [
647
                'filePath' => $filePath,
648
                'uniqid'   => $moduleUniqueID,
649
            ];
650
        }
651
652
        return $res;
653
    }
654
655
    /**
656
     * Install module from file
657
     *
658
     * @param string $filePath
659
     *
660
     * @param string $moduleUniqueID
661
     *
662
     * @return PBXApiResult
663
     */
664
    public static function installModule(string $filePath, string $moduleUniqueID): PBXApiResult
665
    {
666
        $res              = new PBXApiResult();
667
        $res->processor   = __METHOD__;
668
        $res->success     = true;
669
        $currentModuleDir = PbxExtensionUtils::getModuleDir($moduleUniqueID);
670
        $needBackup       = is_dir($currentModuleDir);
671
672
        if ($needBackup) {
673
            self::uninstallModule($moduleUniqueID, true);
674
        }
675
676
        $semZaPath = Util::which('7za');
677
        Util::mwExec("{$semZaPath} e -spf -aoa -o{$currentModuleDir} {$filePath}");
678
        Util::addRegularWWWRights($currentModuleDir);
679
680
        $pbxExtensionSetupClass = "\\Modules\\{$moduleUniqueID}\\Setup\\PbxExtensionSetup";
681
        if (class_exists($pbxExtensionSetupClass)
682
            && method_exists($pbxExtensionSetupClass, 'installModule')) {
683
            $setup = new $pbxExtensionSetupClass($moduleUniqueID);
684
            if ( ! $setup->installModule()) {
685
                $res->success    = false;
686
                $res->messages[] = $setup->getMessages();
687
            }
688
        } else {
689
            $res->success    = false;
690
            $res->messages[] = "Install error: the class {$pbxExtensionSetupClass} not exists";
691
        }
692
693
        if ($res->success) {
694
            $res->data['needRestartWorkers'] = true;
695
        }
696
697
        return $res;
698
    }
699
700
    /**
701
     * Uninstall module
702
     *
703
     * @param string $moduleUniqueID
704
     *
705
     * @param bool   $keepSettings
706
     *
707
     * @return PBXApiResult
708
     */
709
    public static function uninstallModule(string $moduleUniqueID, bool $keepSettings): PBXApiResult
710
    {
711
        $res              = new PBXApiResult();
712
        $res->processor   = __METHOD__;
713
        $currentModuleDir = PbxExtensionUtils::getModuleDir($moduleUniqueID);
714
        // Kill all module processes
715
        if (is_dir("{$currentModuleDir}/bin")) {
716
            $busyboxPath = Util::which('busybox');
717
            $killPath    = Util::which('kill');
718
            $lsofPath    = Util::which('lsof');
719
            $grepPath    = Util::which('grep');
720
            $awkPath     = Util::which('awk');
721
            $uniqPath    = Util::which('uniq');
722
            Util::mwExec(
723
                "{$busyboxPath} {$killPath} -9 $({$lsofPath} {$currentModuleDir}/bin/* |  {$busyboxPath} {$grepPath} -v COMMAND | {$busyboxPath} {$awkPath}  '{ print $2}' | {$busyboxPath} {$uniqPath})"
724
            );
725
        }
726
        // Uninstall module with keep settings and backup db
727
        $moduleClass = "\\Modules\\{$moduleUniqueID}\\Setup\\PbxExtensionSetup";
728
729
        try {
730
            if (class_exists($moduleClass)
731
                && method_exists($moduleClass, 'uninstallModule')) {
732
                $setup = new $moduleClass($moduleUniqueID);
733
            } else {
734
                // Заглушка которая позволяет удалить модуль из базы данных, которого нет на диске
735
                $moduleClass = PbxExtensionSetupFailure::class;
736
                $setup       = new $moduleClass($moduleUniqueID);
737
            }
738
            $setup->uninstallModule($keepSettings);
739
        } finally {
740
            if (is_dir($currentModuleDir)) {
741
                // Broken or very old module. Force uninstall.
742
                $rmPath = Util::which('rm');
743
                Util::mwExec("{$rmPath} -rf {$currentModuleDir}");
744
745
                $moduleClass = PbxExtensionSetupFailure::class;
746
                $setup       = new $moduleClass($moduleUniqueID);
747
                $setup->unregisterModule();
748
            }
749
        }
750
        $res->success                    = true;
751
        $res->data['needRestartWorkers'] = true;
752
753
        return $res;
754
    }
755
756
    /**
757
     *
758
     * Scans a directory just like scandir(), only recursively
759
     * returns a hierarchical array representing the directory structure
760
     *
761
     * @param string $dir directory to scan
762
     *
763
     * @return array
764
     */
765
    public static function scanDirRecursively(string $dir): array
766
    {
767
        $list = [];
768
769
        //get directory contents
770
        foreach (scandir($dir) as $d) {
771
            //ignore any of the files in the array
772
            if (in_array($d, ['.', '..'])) {
773
                continue;
774
            }
775
            //if current file ($d) is a directory, call scanDirRecursively
776
            if (is_dir($dir . '/' . $d)) {
777
                $list[] = self::scanDirRecursively($dir . '/' . $d);
778
                //otherwise, add the file to the list
779
            } elseif (is_file($dir . '/' . $d) || is_link($dir . '/' . $d)) {
780
                $list[] = $dir . '/' . $d;
781
            }
782
        }
783
784
        return $list;
785
    }
786
}