Passed
Push — develop ( 3db452...b7dcac )
by Nikolay
05:45
created

PbxExtensionSetupBase::unInstallFiles()   B

Complexity

Conditions 8
Paths 17

Size

Total Lines 39
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 22
c 0
b 0
f 0
dl 0
loc 39
rs 8.4444
cc 8
nc 17
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 $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}/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}/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}/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
        // Restore Database settings
245
        $backupPath = "{$modulesDir}/Backup/{$this->module_uniqid}";
246
        if (is_dir($backupPath)) {
247
            Util::mwExec("cp -r {$backupPath}/db/* {$this->moduleDir}/db/");
248
        }
249
        return true;
250
    }
251
252
    /**
253
     * Setup ownerships and folder rights
254
     *
255
     * @return bool
256
     */
257
    public function fixFilesRights(): bool
258
    {
259
        // Add regular www rights
260
        Util::addRegularWWWRights($this->moduleDir);
261
262
        // Add executable right to module's binary
263
        $binDir = $this->moduleDir.'/bin';
264
        if (is_dir($binDir)){
265
            Util::addExecutableRights($binDir);
266
        }
267
268
        return true;
269
    }
270
271
    /**
272
     * Создает структуру для хранения настроек модуля в своей модели
273
     * и заполняет настройки по-умолчанию если таблицы не было в системе
274
     * см (unInstallDB)
275
     *
276
     * Регистрирует модуль в PbxExtensionModules
277
     *
278
     * @return bool результат установки
279
     */
280
    public function installDB(): bool
281
    {
282
        return true;
283
    }
284
285
    /**
286
     * Последовательный вызов процедур установки модуля расширения
287
     * с результата удаления
288
     *
289
     * @param $keepSettings bool - сохранять настройки модуля при удалении
290
     *
291
     * @return bool - результат удаления
292
     */
293
    public function uninstallModule($keepSettings = false): bool
294
    {
295
        $result = true;
296
        try {
297
            if ( ! $this->unInstallDB($keepSettings)) {
298
                $this->messages[] = ' unInstallDB error';
299
                $result           = false;
300
            }
301
            if ($result && ! $this->unInstallFiles($keepSettings)) {
302
                $this->messages[] = ' unInstallFiles error';
303
                $result           = false;
304
            }
305
        } catch (Throwable $exception) {
306
            $result         = false;
307
            $this->messages[] = $exception->getMessage();
308
        }
309
310
        return $result;
311
    }
312
313
    /**
314
     * Удаляет запись о модуле из PbxExtensionModules
315
     * Удаляет свою модель
316
     *
317
     * @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...
318
     *
319
     * @return bool результат очистки
320
     */
321
    public function unInstallDB($keepSettings = false): bool
322
    {
323
        return $this->unregisterModule();
324
    }
325
326
    /**
327
     * Удаляет запись о модуле из PbxExtensionModules
328
     *
329
     * @return bool результат очистки
330
     */
331
    public function unregisterModule(): bool
332
    {
333
        $result = true;
334
        $module = PbxExtensionModules::findFirst("uniqid='{$this->module_uniqid}'");
335
        if ($module) {
336
            $result = $result && $module->delete();
337
        }
338
339
        return $result;
340
    }
341
342
    /**
343
     * Выполняет удаление своих файлов с остановной процессов
344
     * при необходимости
345
     *
346
     * @param bool $keepSettings сохранять настройки
347
     *
348
     * @return bool результат удаления
349
     */
350
    public function unInstallFiles($keepSettings = false
351
    )//: bool Пока мешает удалять и обновлять старые модули, раскоменитровать после релиза 2020.5
352
    {
353
        $modulesDir          = $this->config->path('core.modulesDir');
354
        $backupPath = "{$modulesDir}/Backup/{$this->module_uniqid}";
355
        Util::mwExec("rm -rf {$backupPath}");
356
        if ($keepSettings) {
357
            if ( ! is_dir($backupPath) && ! mkdir($backupPath, 0777, true) && ! is_dir($backupPath)) {
358
                $this->messages[] = sprintf('Directory "%s" was not created', $backupPath);
359
360
                return false;
361
            }
362
            Util::mwExec("cp -r {$this->moduleDir}/db {$backupPath}/");
363
        }
364
        Util::mwExec("rm -rf {$this->moduleDir}");
365
366
        // Remove assets
367
        // IMG
368
        $imgCacheDir = appPath('sites/admin-cabinet/assets/img/cache');
369
        $moduleImageCacheDir = "{$imgCacheDir}/{$this->module_uniqid}";
370
        if (file_exists($moduleImageCacheDir)){
371
            unlink($moduleImageCacheDir);
372
        }
373
374
        // CSS
375
        $cssCacheDir = appPath('sites/admin-cabinet/assets/css/cache');
376
        $moduleCSSCacheDir = "{$cssCacheDir}/{$this->module_uniqid}";
377
        if (file_exists($moduleCSSCacheDir)){
378
            unlink($moduleCSSCacheDir);
379
        }
380
381
        // JS
382
        $jsCacheDir = appPath('sites/admin-cabinet/assets/js/cache');
383
        $moduleJSCacheDir = "{$jsCacheDir}/{$this->module_uniqid}";
384
        if (file_exists($moduleJSCacheDir)){
385
            unlink($moduleJSCacheDir);
386
        }
387
388
        return true;
389
    }
390
391
    /**
392
     * Returns error messages
393
     *
394
     * @return array
395
     */
396
    public function getMessages(): array
397
    {
398
        return $this->messages;
399
    }
400
401
    /**
402
     * Выполняет регистрацию модуля в таблице PbxExtensionModules
403
     *
404
     * @return bool
405
     */
406
    public function registerNewModule(): bool
407
    {
408
        // Проверим версию АТС и Модуля на совместимость
409
        $currentVersionPBX = PbxSettings::getValueByKey('PBXVersion');
410
        $currentVersionPBX = str_replace('-dev', '', $currentVersionPBX);
411
        if (version_compare($currentVersionPBX, $this->min_pbx_version) < 0) {
412
            $this->messages[] = "Error: module depends minimum PBX ver $this->min_pbx_version";
413
414
            return false;
415
        }
416
417
        $module = PbxExtensionModules::findFirst("uniqid='{$this->module_uniqid}'");
418
        if ( ! $module) {
419
            $module           = new PbxExtensionModules();
420
            $module->name     = $this->locString("Breadcrumb{$this->module_uniqid}");
421
            $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...
422
        }
423
        $module->uniqid        = $this->module_uniqid;
424
        $module->developer     = $this->developer;
425
        $module->version       = $this->version;
426
        $module->description   = $this->locString("SubHeader{$this->module_uniqid}");
427
        $module->support_email = $this->support_email;
428
429
        return $module->save();
430
    }
431
432
    /**
433
     * Возвращает перевод идентификатора на язык установленный в настройках PBX
434
     *
435
     * @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...
436
     *
437
     * @return string - перевод
438
     */
439
    public function locString($stringId): string
440
    {
441
        $language             = substr(PbxSettings::getValueByKey('WebAdminLanguage'), 0, 2);
442
        $translates           = [];
443
        $extensionsTranslates = [[]];
444
        $results              = glob($this->moduleDir . '/{Messages}/en.php', GLOB_BRACE);
445
        foreach ($results as $path) {
446
            $langArr = require $path;
447
            if (is_array($langArr)) {
448
                $extensionsTranslates[] = $langArr;
449
            }
450
        }
451
        if ($extensionsTranslates !== [[]]) {
452
            $translates = array_merge($translates, ...$extensionsTranslates);
453
        }
454
        if ($language !== 'en') {
455
            $additionalTranslates = [[]];
456
            $results              = glob($this->moduleDir . "/{Messages}/{$language}.php", GLOB_BRACE);
457
            foreach ($results as $path) {
458
                $langArr = require $path;
459
                if (is_array($langArr)) {
460
                    $additionalTranslates[] = $langArr;
461
                }
462
            }
463
            if ($additionalTranslates !== [[]]) {
464
                $translates = array_merge($translates, ...$additionalTranslates);
465
            }
466
        }
467
468
        // Return a translation object
469
        if (array_key_exists($stringId, $translates)) {
470
            return $translates[$stringId];
471
        }
472
473
        return $stringId;
474
    }
475
476
    /**
477
     * Обходит файлы с описанием моделей и создает таблицы в базе данных
478
     *
479
     * @return bool
480
     */
481
    public function createSettingsTableByModelsAnnotations(): bool
482
    {
483
        $result  = true;
484
485
        // Add new connection for this module after add new Models folder
486
        RegisterDIServices::recreateModulesDBConnections();
487
488
        $results = glob($this->moduleDir . '/Models/*.php', GLOB_NOSORT);
489
        $dbUpgrade = new UpdateDatabase();
490
        foreach ($results as $file) {
491
            $className        = pathinfo($file)['filename'];
492
            $moduleModelClass = "\\Modules\\{$this->module_uniqid}\\Models\\{$className}";
493
            $dbUpgrade->createUpdateDbTableByAnnotations($moduleModelClass);
494
        }
495
        if ($result){
0 ignored issues
show
introduced by
The condition $result is always true.
Loading history...
496
            // Update database connections after upgrade their structure
497
            RegisterDIServices::recreateModulesDBConnections();
498
        }
499
        return $result;
500
    }
501
502
503
    /**
504
     * Добавляет модуль в боковое меню
505
     *
506
     * @return bool
507
     */
508
    public function addToSidebar(): bool
509
    {
510
        $menuSettingsKey           = "AdditionalMenuItem{$this->module_uniqid}";
511
        $unCamelizedControllerName = Text::uncamelize($this->module_uniqid, '-');
512
        $menuSettings              = PbxSettings::findFirstByKey($menuSettingsKey);
513
        if ($menuSettings === null) {
514
            $menuSettings      = new PbxSettings();
515
            $menuSettings->key = $menuSettingsKey;
516
        }
517
        $value               = [
518
            'uniqid'        => $this->module_uniqid,
519
            'href'          => "/admin-cabinet/$unCamelizedControllerName",
520
            'group'         => 'maintenance',
521
            'iconClass'     => 'puzzle',
522
            'caption'       => "Breadcrumb$this->module_uniqid",
523
            'showAtSidebar' => true,
524
        ];
525
        $menuSettings->value = json_encode($value);
526
527
        return $menuSettings->save();
528
    }
529
}