Passed
Push — develop ( 3c5942...636dd4 )
by Nikolay
04:57
created

PbxExtensionSetupBase::unInstallFiles()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 37
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 22
dl 0
loc 37
rs 9.2568
c 1
b 0
f 0
cc 5
nc 16
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, 12 2019
7
 */
8
9
namespace MikoPBX\Modules\Setup;
10
11
use MikoPBX\Core\Config\RegisterDIServices;
12
use MikoPBX\Core\System\Upgrade\UpdateDatabase;
13
use MikoPBX\Common\Models\{PbxExtensionModules, PbxSettings};
14
use MikoPBX\Core\System\Util;
15
use Phalcon\DI;
16
use Phalcon\Di\Exception;
17
use Phalcon\Text;
18
use Throwable;
19
20
use function MikoPBX\Common\Config\appPath;
21
22
/**
23
 * Class PbxExtensionSetupBase
24
 * Общие для всех модулей методы
25
 * Подключается при установке, удалении модуля
26
 */
27
abstract class PbxExtensionSetupBase implements PbxExtensionSetupInterface
28
{
29
    /**
30
     * Trial product version identify number from module.json
31
     *
32
     * @var int
33
     */
34
    public $lic_product_id;
35
    /**
36
     * License feature identify number from module.json
37
     *
38
     * @var int
39
     */
40
    public $lic_feature_id;
41
    /**
42
     * Module unique identify  from module.json
43
     *
44
     * @var string
45
     */
46
    protected string $module_uniqid;
47
    /**
48
     * Module version from module.json
49
     *
50
     * @var string
51
     */
52
    protected $version;
53
    /**
54
     * Minimal require version PBX
55
     *
56
     * @var string
57
     */
58
    protected $min_pbx_version;
59
    /**
60
     * Module developer name
61
     *
62
     * @var string
63
     */
64
    protected $developer;
65
    /**
66
     * Module developer's email from module.json
67
     *
68
     * @var string
69
     */
70
    protected $support_email;
71
    /**
72
     * PBX general database
73
     *
74
     * @var \Phalcon\Db\Adapter\Pdo\Sqlite
75
     */
76
    protected $db;
77
78
79
    /**
80
     * Folder with module files
81
     *
82
     * @var string
83
     */
84
    protected $moduleDir;
85
86
    /**
87
     * Phalcon config service
88
     *
89
     * @var \Phalcon\Config
90
     */
91
    protected $config;
92
93
    /**
94
     * License worker
95
     *
96
     * @var \MikoPBX\Service\License
97
     */
98
    protected $license;
99
100
    /**
101
     * Dependency injector
102
     *
103
     * @var \Phalcon\DI
104
     */
105
    private $di;
106
107
    /**
108
     * Error and verbose messages
109
     *
110
     * @var array
111
     */
112
    private $messages;
113
114
    /**
115
     * PbxExtensionBase constructor.
116
     *
117
     * @param $module_uniqid
118
     *
119
     * @throws \Phalcon\Exception
120
     */
121
    public function __construct($module_uniqid = null)
122
    {
123
        if ($module_uniqid !== null) {
124
            $this->module_uniqid = $module_uniqid;
125
        }
126
        $this->di      = DI::getDefault();
127
        if ($this->di === null){
128
            throw new Exception('\Phalcon\DI did not installed.');
129
        }
130
        $this->db      = $this->di->getShared('db');
131
        $this->config  = $this->di->getShared('config');
132
        $this->license =  $this->di->getShared('license');
133
        $this->moduleDir = $this->config->path('core.modulesDir') . '/' . $this->module_uniqid;
134
        $settings_file = "{$this->moduleDir}/module.json";
135
        if (file_exists($settings_file)) {
136
            $module_settings = json_decode(file_get_contents($settings_file), true);
137
            if ($module_settings) {
138
                $this->version         = $module_settings['version'];
139
                $this->min_pbx_version = $module_settings['min_pbx_version'];
140
                $this->developer       = $module_settings['developer'];
141
                $this->support_email   = $module_settings['support_email'];
142
                if (array_key_exists('lic_product_id', $module_settings)) {
143
                    $this->lic_product_id = $module_settings['lic_product_id'];
144
                } else {
145
                    $this->lic_product_id = 0;
146
                }
147
                if (array_key_exists('lic_feature_id', $module_settings)) {
148
                    $this->lic_feature_id = $module_settings['lic_feature_id'];
149
                } else {
150
                    $this->lic_feature_id = 0;
151
                }
152
            } else {
153
                $this->messages[] = 'Error on decode module.json';
154
            }
155
        }
156
157
        $this->messages  = [];
158
159
160
    }
161
162
    /**
163
     * Последовательный вызов процедур установки модуля расширения
164
     * с текстового результата установки
165
     *
166
     * @return bool - результат установки
167
     */
168
    public function installModule(): bool
169
    {
170
        $result = true;
171
        try {
172
            if ( ! $this->activateLicense()) {
173
                $this->messages[] = 'License activate error';
174
                $result           = false;
175
            }
176
            if ( ! $this->installFiles()) {
177
                $this->messages[] = ' installFiles error';
178
                $result           = false;
179
            }
180
            if ( ! $this->installDB()) {
181
                $this->messages[] = ' installDB error';
182
                $result           = false;
183
            }
184
            $this->fixFilesRights();
185
        } catch (Throwable $exception) {
186
            $result         = false;
187
            $this->messages[] = $exception->getMessage();
188
        }
189
190
        return $result;
191
    }
192
193
    /**
194
     * Выполняет активацию триалов, проверку лицензионного клчюча
195
     *
196
     * @return bool результат активации лицензии
197
     */
198
    public function activateLicense(): bool
199
    {
200
        return true;
201
    }
202
203
    /**
204
     * Выполняет копирование необходимых файлов, в папки системы
205
     *
206
     * @return bool результат установки
207
     */
208
    public function installFiles(): bool
209
    {
210
        // Create cache links for JS, CSS, IMG folders
211
212
        $modulesDir          = $this->config->path('core.modulesDir');
213
        // IMG
214
        $moduleImageDir      = "{$this->moduleDir}/public/assets/img";
215
        $imgCacheDir = appPath('sites/admin-cabinet/assets/img/cache');
216
        $moduleImageCacheDir = "{$imgCacheDir}/{$this->module_uniqid}";
217
        if (file_exists($moduleImageCacheDir)){
218
            unlink($moduleImageCacheDir);
219
        }
220
        if (file_exists($moduleImageDir)) {
221
            symlink($moduleImageDir, $moduleImageCacheDir);
222
        }
223
        // CSS
224
        $moduleCSSDir      = "{$this->moduleDir}/public/assets/css";
225
        $cssCacheDir = appPath('sites/admin-cabinet/assets/css/cache');
226
        $moduleCSSCacheDir = "{$cssCacheDir}/{$this->module_uniqid}";
227
        if (file_exists($moduleCSSCacheDir)){
228
            unlink($moduleCSSCacheDir);
229
        }
230
        if (file_exists($moduleCSSDir)) {
231
            symlink($moduleCSSDir, $moduleCSSCacheDir);
232
        }
233
        // JS
234
        $moduleJSDir      = "{$this->moduleDir}/public/assets/js";
235
        $jsCacheDir = appPath('sites/admin-cabinet/assets/js/cache');
236
        $moduleJSCacheDir = "{$jsCacheDir}/{$this->module_uniqid}";
237
        if (file_exists($moduleJSCacheDir)){
238
            unlink($moduleJSCacheDir);
239
        }
240
        if (file_exists($moduleJSDir)) {
241
            symlink($moduleJSDir, $moduleJSCacheDir);
242
        }
243
244
        // Create symlinks to AGI-BIN
245
        $agiBinDir = $this->config->path('asterisk.astagidir');
246
        $moduleAgiBinDir      = "{$this->moduleDir}/agi-bin";
247
        $files = glob("$moduleAgiBinDir/*.{php}", GLOB_BRACE);
248
        foreach($files as $file) {
249
            $newFilename = $agiBinDir.'/'. pathinfo($file)['filename'];
250
            Util::createUpdateSymlink($file, $newFilename);
251
        }
252
253
        // Restore Database settings
254
        $backupPath = "{$modulesDir}/Backup/{$this->module_uniqid}";
255
        if (is_dir($backupPath)) {
256
            $cpPath = Util::which('cp');
257
            Util::mwExec("{$cpPath} -r {$backupPath}/db/* {$this->moduleDir}/db/");
258
        }
259
        return true;
260
    }
261
262
    /**
263
     * Setup ownerships and folder rights
264
     *
265
     * @return bool
266
     */
267
    public function fixFilesRights(): bool
268
    {
269
        // Add regular www rights
270
        Util::addRegularWWWRights($this->moduleDir);
271
272
        // Add executable right to module's binary
273
        $binDir = $this->moduleDir.'/bin';
274
        if (is_dir($binDir)){
275
            Util::addExecutableRights($binDir);
276
        }
277
278
        return true;
279
    }
280
281
    /**
282
     * Создает структуру для хранения настроек модуля в своей модели
283
     * и заполняет настройки по-умолчанию если таблицы не было в системе
284
     * см (unInstallDB)
285
     *
286
     * Регистрирует модуль в PbxExtensionModules
287
     *
288
     * @return bool результат установки
289
     */
290
    public function installDB(): bool
291
    {
292
        return true;
293
    }
294
295
    /**
296
     * Последовательный вызов процедур установки модуля расширения
297
     * с результата удаления
298
     *
299
     * @param $keepSettings bool - сохранять настройки модуля при удалении
300
     *
301
     * @return bool - результат удаления
302
     */
303
    public function uninstallModule($keepSettings = false): bool
304
    {
305
        $result = true;
306
        try {
307
            if ( ! $this->unInstallDB($keepSettings)) {
308
                $this->messages[] = ' unInstallDB error';
309
                $result           = false;
310
            }
311
            if ($result && ! $this->unInstallFiles($keepSettings)) {
312
                $this->messages[] = ' unInstallFiles error';
313
                $result           = false;
314
            }
315
        } catch (Throwable $exception) {
316
            $result         = false;
317
            $this->messages[] = $exception->getMessage();
318
        }
319
320
        return $result;
321
    }
322
323
    /**
324
     * Удаляет запись о модуле из PbxExtensionModules
325
     * Удаляет свою модель
326
     *
327
     * @param  $keepSettings - оставляет таблицу с данными своей модели
0 ignored issues
show
Documentation Bug introduced by
The doc comment - at position 0 could not be parsed: Unknown type name '-' at position 0 in -.
Loading history...
328
     *
329
     * @return bool результат очистки
330
     */
331
    public function unInstallDB($keepSettings = false): bool
332
    {
333
        return $this->unregisterModule();
334
    }
335
336
    /**
337
     * Удаляет запись о модуле из PbxExtensionModules
338
     *
339
     * @return bool результат очистки
340
     */
341
    public function unregisterModule(): bool
342
    {
343
        $result = true;
344
        $module = PbxExtensionModules::findFirst("uniqid='{$this->module_uniqid}'");
345
        if ($module) {
346
            $result = $result && $module->delete();
347
        }
348
349
        return $result;
350
    }
351
352
    /**
353
     * Выполняет удаление своих файлов с остановной процессов
354
     * при необходимости
355
     *
356
     * @param bool $keepSettings сохранять настройки
357
     *
358
     * @return bool результат удаления
359
     */
360
    public function unInstallFiles($keepSettings = false
361
    )//: bool Пока мешает удалять и обновлять старые модули, раскоменитровать после релиза 2020.5
362
    {
363
        $cpPath = Util::which('cp');
364
        $rmPath = Util::which('rm');
365
        $modulesDir          = $this->config->path('core.modulesDir');
366
        $backupPath = "{$modulesDir}/Backup/{$this->module_uniqid}";
367
        Util::mwExec("{$rmPath} -rf {$backupPath}");
368
        if ($keepSettings) {
369
            Util::mwMkdir($backupPath);
370
            Util::mwExec("{$cpPath} -r {$this->moduleDir}/db {$backupPath}/");
371
        }
372
        Util::mwExec("{$rmPath} -rf {$this->moduleDir}");
373
374
        // Remove assets
375
        // IMG
376
        $imgCacheDir = appPath('sites/admin-cabinet/assets/img/cache');
377
        $moduleImageCacheDir = "{$imgCacheDir}/{$this->module_uniqid}";
378
        if (file_exists($moduleImageCacheDir)){
379
            unlink($moduleImageCacheDir);
380
        }
381
382
        // CSS
383
        $cssCacheDir = appPath('sites/admin-cabinet/assets/css/cache');
384
        $moduleCSSCacheDir = "{$cssCacheDir}/{$this->module_uniqid}";
385
        if (file_exists($moduleCSSCacheDir)){
386
            unlink($moduleCSSCacheDir);
387
        }
388
389
        // JS
390
        $jsCacheDir = appPath('sites/admin-cabinet/assets/js/cache');
391
        $moduleJSCacheDir = "{$jsCacheDir}/{$this->module_uniqid}";
392
        if (file_exists($moduleJSCacheDir)){
393
            unlink($moduleJSCacheDir);
394
        }
395
396
        return true;
397
    }
398
399
    /**
400
     * Returns error messages
401
     *
402
     * @return array
403
     */
404
    public function getMessages(): array
405
    {
406
        return $this->messages;
407
    }
408
409
    /**
410
     * Выполняет регистрацию модуля в таблице PbxExtensionModules
411
     *
412
     * @return bool
413
     */
414
    public function registerNewModule(): bool
415
    {
416
        // Проверим версию АТС и Модуля на совместимость
417
        $currentVersionPBX = PbxSettings::getValueByKey('PBXVersion');
418
        $currentVersionPBX = str_replace('-dev', '', $currentVersionPBX);
419
        if (version_compare($currentVersionPBX, $this->min_pbx_version) < 0) {
420
            $this->messages[] = "Error: module depends minimum PBX ver $this->min_pbx_version";
421
422
            return false;
423
        }
424
425
        $module = PbxExtensionModules::findFirst("uniqid='{$this->module_uniqid}'");
426
        if ( ! $module) {
427
            $module           = new PbxExtensionModules();
428
            $module->name     = $this->locString("Breadcrumb{$this->module_uniqid}");
429
            $module->disabled = '1';
0 ignored issues
show
Documentation Bug introduced by
It seems like '1' of type string is incompatible with the declared type integer|null of property $disabled.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
430
        }
431
        $module->uniqid        = $this->module_uniqid;
432
        $module->developer     = $this->developer;
433
        $module->version       = $this->version;
434
        $module->description   = $this->locString("SubHeader{$this->module_uniqid}");
435
        $module->support_email = $this->support_email;
436
437
        return $module->save();
438
    }
439
440
    /**
441
     * Возвращает перевод идентификатора на язык установленный в настройках PBX
442
     *
443
     * @param $stringId - идентификатор фразы
0 ignored issues
show
Documentation Bug introduced by
The doc comment - at position 0 could not be parsed: Unknown type name '-' at position 0 in -.
Loading history...
444
     *
445
     * @return string - перевод
446
     */
447
    public function locString($stringId): string
448
    {
449
        $language             = substr(PbxSettings::getValueByKey('WebAdminLanguage'), 0, 2);
450
        $translates           = [];
451
        $extensionsTranslates = [[]];
452
        $results              = glob($this->moduleDir . '/{Messages}/en.php', GLOB_BRACE);
453
        foreach ($results as $path) {
454
            $langArr = require $path;
455
            if (is_array($langArr)) {
456
                $extensionsTranslates[] = $langArr;
457
            }
458
        }
459
        if ($extensionsTranslates !== [[]]) {
460
            $translates = array_merge($translates, ...$extensionsTranslates);
461
        }
462
        if ($language !== 'en') {
463
            $additionalTranslates = [[]];
464
            $results              = glob($this->moduleDir . "/{Messages}/{$language}.php", GLOB_BRACE);
465
            foreach ($results as $path) {
466
                $langArr = require $path;
467
                if (is_array($langArr)) {
468
                    $additionalTranslates[] = $langArr;
469
                }
470
            }
471
            if ($additionalTranslates !== [[]]) {
472
                $translates = array_merge($translates, ...$additionalTranslates);
473
            }
474
        }
475
476
        // Return a translation object
477
        if (array_key_exists($stringId, $translates)) {
478
            return $translates[$stringId];
479
        }
480
481
        return $stringId;
482
    }
483
484
    /**
485
     * Обходит файлы с описанием моделей и создает таблицы в базе данных
486
     *
487
     * @return bool
488
     */
489
    public function createSettingsTableByModelsAnnotations(): bool
490
    {
491
492
        // Add new connection for this module after add new Models folder
493
        RegisterDIServices::recreateModulesDBConnections();
494
495
        $results = glob($this->moduleDir . '/Models/*.php', GLOB_NOSORT);
496
        $dbUpgrade = new UpdateDatabase();
497
        foreach ($results as $file) {
498
            $className        = pathinfo($file)['filename'];
499
            $moduleModelClass = "\\Modules\\{$this->module_uniqid}\\Models\\{$className}";
500
            $upgradeResult = $dbUpgrade->createUpdateDbTableByAnnotations($moduleModelClass);
501
            if (!$upgradeResult){
502
                return false;
503
            }
504
505
        }
506
        // Update database connections after upgrade their structure
507
        RegisterDIServices::recreateModulesDBConnections();
508
509
        return true;
510
    }
511
512
513
    /**
514
     * Добавляет модуль в боковое меню
515
     *
516
     * @return bool
517
     */
518
    public function addToSidebar(): bool
519
    {
520
        $menuSettingsKey           = "AdditionalMenuItem{$this->module_uniqid}";
521
        $unCamelizedControllerName = Text::uncamelize($this->module_uniqid, '-');
522
        $menuSettings              = PbxSettings::findFirstByKey($menuSettingsKey);
523
        if ($menuSettings === null) {
524
            $menuSettings      = new PbxSettings();
525
            $menuSettings->key = $menuSettingsKey;
526
        }
527
        $value               = [
528
            'uniqid'        => $this->module_uniqid,
529
            'href'          => "/admin-cabinet/$unCamelizedControllerName",
530
            'group'         => 'maintenance',
531
            'iconClass'     => 'puzzle',
532
            'caption'       => "Breadcrumb$this->module_uniqid",
533
            'showAtSidebar' => true,
534
        ];
535
        $menuSettings->value = json_encode($value);
536
537
        return $menuSettings->save();
538
    }
539
}