Passed
Push — develop ( eb8832...f8f730 )
by Nikolay
05:34
created

FilesManagementProcessor::downloadNewFirmware()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 46
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

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