Passed
Push — develop ( 180af8...621c13 )
by Nikolay
08:11
created

PbxExtensionSetupBase::__construct()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 32
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 25
c 1
b 0
f 0
dl 0
loc 32
rs 9.2088
cc 5
nc 6
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\Injectable;
16
use Phalcon\Text;
17
18
use function MikoPBX\Common\Config\appPath;
19
20
/**
21
 * Class PbxExtensionSetupBase
22
 * Общие для всех модулей методы
23
 * Подключается при установке, удалении модуля
24
 */
25
abstract class PbxExtensionSetupBase extends Injectable implements PbxExtensionSetupInterface
26
{
27
    /**
28
     * Trial product version identify number from module.json
29
     *
30
     * @var int
31
     */
32
    public $lic_product_id;
33
    /**
34
     * License feature identify number from module.json
35
     *
36
     * @var int
37
     */
38
    public $lic_feature_id;
39
    /**
40
     * Module unique identify  from module.json
41
     *
42
     * @var string
43
     */
44
    protected string $moduleUniqueID;
45
    /**
46
     * Module version from module.json
47
     *
48
     * @var string
49
     */
50
    protected $version;
51
    /**
52
     * Minimal require version PBX
53
     *
54
     * @var string
55
     */
56
    protected $min_pbx_version;
57
    /**
58
     * Module developer name
59
     *
60
     * @var string
61
     */
62
    protected $developer;
63
    /**
64
     * Module developer's email from module.json
65
     *
66
     * @var string
67
     */
68
    protected $support_email;
69
    /**
70
     * PBX general database
71
     *
72
     * @var \Phalcon\Db\Adapter\Pdo\Sqlite
73
     */
74
    protected $db;
75
76
77
    /**
78
     * Folder with module files
79
     *
80
     * @var string
81
     */
82
    protected string $moduleDir;
83
84
    /**
85
     * Phalcon config service
86
     *
87
     * @var \Phalcon\Config
88
     */
89
    protected $config;
90
91
    /**
92
     * License worker
93
     *
94
     * @var \MikoPBX\Service\License
95
     */
96
    protected $license;
97
98
    /**
99
     * Error and verbose messages
100
     *
101
     * @var array
102
     */
103
    protected array $messages;
104
105
    /**
106
     * PbxExtensionBase constructor.
107
     *
108
     * @param string $moduleUniqueID
109
     */
110
    public function __construct(string $moduleUniqueID)
111
    {
112
        $this->moduleUniqueID = $moduleUniqueID;
113
        $this->messages = [];
114
        $this->db      = $this->di->getShared('db');
115
        $this->config  = $this->di->getShared('config');
116
        $this->license =  $this->di->getShared('license');
117
        $this->moduleDir = $this->config->path('core.modulesDir') . '/' . $this->moduleUniqueID;
118
        $settings_file = "{$this->moduleDir}/module.json";
119
        if (file_exists($settings_file)) {
120
            $module_settings = json_decode(file_get_contents($settings_file), true);
121
            if ($module_settings) {
122
                $this->version         = $module_settings['version'];
123
                $this->min_pbx_version = $module_settings['min_pbx_version'];
124
                $this->developer       = $module_settings['developer'];
125
                $this->support_email   = $module_settings['support_email'];
126
                if (array_key_exists('lic_product_id', $module_settings)) {
127
                    $this->lic_product_id = $module_settings['lic_product_id'];
128
                } else {
129
                    $this->lic_product_id = 0;
130
                }
131
                if (array_key_exists('lic_feature_id', $module_settings)) {
132
                    $this->lic_feature_id = $module_settings['lic_feature_id'];
133
                } else {
134
                    $this->lic_feature_id = 0;
135
                }
136
            } else {
137
                $this->messages[] = 'Error on decode module.json';
138
            }
139
        }
140
141
        $this->messages  = [];
142
143
144
    }
145
146
    /**
147
     * Последовательный вызов процедур установки модуля расширения
148
     * с текстового результата установки
149
     *
150
     * @return bool - результат установки
151
     */
152
    public function installModule(): bool
153
    {
154
        $result = true;
155
        try {
156
            if ( ! $this->activateLicense()) {
157
                $this->messages[] = 'License activate error';
158
                $result           = false;
159
            }
160
            if ( ! $this->installFiles()) {
161
                $this->messages[] = ' installFiles error';
162
                $result           = false;
163
            }
164
            if ( ! $this->installDB()) {
165
                $this->messages[] = ' installDB error';
166
                $result           = false;
167
            }
168
            $this->fixFilesRights();
169
        } catch (\Exception $exception) {
170
            $result         = false;
171
            $this->messages[] = $exception->getMessage();
172
        }
173
174
        return $result;
175
    }
176
177
    /**
178
     * Выполняет активацию триалов, проверку лицензионного клчюча
179
     *
180
     * @return bool результат активации лицензии
181
     */
182
    public function activateLicense(): bool
183
    {
184
        return true;
185
    }
186
187
    /**
188
     * Выполняет копирование необходимых файлов, в папки системы
189
     *
190
     * @return bool результат установки
191
     */
192
    public function installFiles(): bool
193
    {
194
        // Create cache links for JS, CSS, IMG folders
195
196
        $modulesDir          = $this->config->path('core.modulesDir');
197
        // IMG
198
        $moduleImageDir      = "{$this->moduleDir}/public/assets/img";
199
        $imgCacheDir = appPath('sites/admin-cabinet/assets/img/cache');
200
        $moduleImageCacheDir = "{$imgCacheDir}/{$this->moduleUniqueID}";
201
        if (file_exists($moduleImageCacheDir)){
202
            unlink($moduleImageCacheDir);
203
        }
204
        if (file_exists($moduleImageDir)) {
205
            symlink($moduleImageDir, $moduleImageCacheDir);
206
        }
207
        // CSS
208
        $moduleCSSDir      = "{$this->moduleDir}/public/assets/css";
209
        $cssCacheDir = appPath('sites/admin-cabinet/assets/css/cache');
210
        $moduleCSSCacheDir = "{$cssCacheDir}/{$this->moduleUniqueID}";
211
        if (file_exists($moduleCSSCacheDir)){
212
            unlink($moduleCSSCacheDir);
213
        }
214
        if (file_exists($moduleCSSDir)) {
215
            symlink($moduleCSSDir, $moduleCSSCacheDir);
216
        }
217
        // JS
218
        $moduleJSDir      = "{$this->moduleDir}/public/assets/js";
219
        $jsCacheDir = appPath('sites/admin-cabinet/assets/js/cache');
220
        $moduleJSCacheDir = "{$jsCacheDir}/{$this->moduleUniqueID}";
221
        if (file_exists($moduleJSCacheDir)){
222
            unlink($moduleJSCacheDir);
223
        }
224
        if (file_exists($moduleJSDir)) {
225
            symlink($moduleJSDir, $moduleJSCacheDir);
226
        }
227
228
        // Create symlinks to AGI-BIN
229
        $agiBinDir = $this->config->path('asterisk.astagidir');
230
        $moduleAgiBinDir      = "{$this->moduleDir}/agi-bin";
231
        $files = glob("$moduleAgiBinDir/*.{php}", GLOB_BRACE);
232
        foreach($files as $file) {
233
            $newFilename = $agiBinDir.'/'. pathinfo($file)['filename'];
234
            Util::createUpdateSymlink($file, $newFilename);
235
        }
236
237
        // Restore Database settings
238
        $backupPath = "{$modulesDir}/Backup/{$this->moduleUniqueID}";
239
        if (is_dir($backupPath)) {
240
            $cpPath = Util::which('cp');
241
            Util::mwExec("{$cpPath} -r {$backupPath}/db/* {$this->moduleDir}/db/");
242
        }
243
        return true;
244
    }
245
246
    /**
247
     * Setup ownerships and folder rights
248
     *
249
     * @return bool
250
     */
251
    public function fixFilesRights(): bool
252
    {
253
        // Add regular www rights
254
        Util::addRegularWWWRights($this->moduleDir);
255
256
        // Add executable right to module's binary
257
        $binDir = $this->moduleDir.'/bin';
258
        if (is_dir($binDir)){
259
            Util::addExecutableRights($binDir);
260
        }
261
262
        return true;
263
    }
264
265
    /**
266
     * Создает структуру для хранения настроек модуля в своей модели
267
     * и заполняет настройки по-умолчанию если таблицы не было в системе
268
     * см (unInstallDB)
269
     *
270
     * Регистрирует модуль в PbxExtensionModules
271
     *
272
     * @return bool результат установки
273
     */
274
    public function installDB(): bool
275
    {
276
        return true;
277
    }
278
279
    /**
280
     * Последовательный вызов процедур установки модуля расширения
281
     * с результата удаления
282
     *
283
     * @param $keepSettings bool - сохранять настройки модуля при удалении
284
     *
285
     * @return bool - результат удаления
286
     */
287
    public function uninstallModule($keepSettings = false): bool
288
    {
289
        $result = true;
290
        try {
291
            if ( ! $this->unInstallDB($keepSettings)) {
292
                $this->messages[] = ' unInstallDB error';
293
                $result           = false;
294
            }
295
            if ($result && ! $this->unInstallFiles($keepSettings)) {
296
                $this->messages[] = ' unInstallFiles error';
297
                $result           = false;
298
            }
299
        } catch (\Exception $exception) {
300
            $result         = false;
301
            $this->messages[] = $exception->getMessage();
302
        }
303
304
        return $result;
305
    }
306
307
    /**
308
     * Удаляет запись о модуле из PbxExtensionModules
309
     * Удаляет свою модель
310
     *
311
     * @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...
312
     *
313
     * @return bool результат очистки
314
     */
315
    public function unInstallDB($keepSettings = false): bool
316
    {
317
        return $this->unregisterModule();
318
    }
319
320
    /**
321
     * Удаляет запись о модуле из PbxExtensionModules
322
     *
323
     * @return bool результат очистки
324
     */
325
    public function unregisterModule(): bool
326
    {
327
        $result = true;
328
        $module = PbxExtensionModules::findFirstByUniqid($this->moduleUniqueID);
329
        if ($module) {
330
            $result = $result && $module->delete();
331
        }
332
333
        return $result;
334
    }
335
336
    /**
337
     * Выполняет удаление своих файлов с остановной процессов
338
     * при необходимости
339
     *
340
     * @param bool $keepSettings сохранять настройки
341
     *
342
     * @return bool результат удаления
343
     */
344
    public function unInstallFiles($keepSettings = false):bool
345
    {
346
        $cpPath = Util::which('cp');
347
        $rmPath = Util::which('rm');
348
        $modulesDir          = $this->config->path('core.modulesDir');
349
        $backupPath = "{$modulesDir}/Backup/{$this->moduleUniqueID}";
350
        Util::mwExec("{$rmPath} -rf {$backupPath}");
351
        if ($keepSettings) {
352
            Util::mwMkdir($backupPath);
353
            Util::mwExec("{$cpPath} -r {$this->moduleDir}/db {$backupPath}/");
354
        }
355
        Util::mwExec("{$rmPath} -rf {$this->moduleDir}");
356
357
        // Remove assets
358
        // IMG
359
        $imgCacheDir = appPath('sites/admin-cabinet/assets/img/cache');
360
        $moduleImageCacheDir = "{$imgCacheDir}/{$this->moduleUniqueID}";
361
        if (file_exists($moduleImageCacheDir)){
362
            unlink($moduleImageCacheDir);
363
        }
364
365
        // CSS
366
        $cssCacheDir = appPath('sites/admin-cabinet/assets/css/cache');
367
        $moduleCSSCacheDir = "{$cssCacheDir}/{$this->moduleUniqueID}";
368
        if (file_exists($moduleCSSCacheDir)){
369
            unlink($moduleCSSCacheDir);
370
        }
371
372
        // JS
373
        $jsCacheDir = appPath('sites/admin-cabinet/assets/js/cache');
374
        $moduleJSCacheDir = "{$jsCacheDir}/{$this->moduleUniqueID}";
375
        if (file_exists($moduleJSCacheDir)){
376
            unlink($moduleJSCacheDir);
377
        }
378
379
        return true;
380
    }
381
382
    /**
383
     * Returns error messages
384
     *
385
     * @return array
386
     */
387
    public function getMessages(): array
388
    {
389
        return $this->messages;
390
    }
391
392
    /**
393
     * Выполняет регистрацию модуля в таблице PbxExtensionModules
394
     *
395
     * @return bool
396
     */
397
    public function registerNewModule(): bool
398
    {
399
        // Проверим версию АТС и Модуля на совместимость
400
        $currentVersionPBX = PbxSettings::getValueByKey('PBXVersion');
401
        $currentVersionPBX = str_replace('-dev', '', $currentVersionPBX);
402
        if (version_compare($currentVersionPBX, $this->min_pbx_version) < 0) {
403
            $this->messages[] = "Module depends minimum PBX ver $this->min_pbx_version";
404
405
            return false;
406
        }
407
408
        $module = PbxExtensionModules::findFirstByUniqid($this->moduleUniqueID);
409
        if ( ! $module) {
410
            $module           = new PbxExtensionModules();
411
            $module->name     = $this->locString("Breadcrumb{$this->moduleUniqueID}");
412
            $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...
413
        }
414
        $module->uniqid        = $this->moduleUniqueID;
415
        $module->developer     = $this->developer;
416
        $module->version       = $this->version;
417
        $module->description   = $this->locString("SubHeader{$this->moduleUniqueID}");
418
        $module->support_email = $this->support_email;
419
420
        return $module->save();
421
    }
422
423
    /**
424
     * Возвращает перевод идентификатора на язык установленный в настройках PBX
425
     *
426
     * @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...
427
     *
428
     * @return string - перевод
429
     */
430
    public function locString($stringId): string
431
    {
432
        $language             = substr(PbxSettings::getValueByKey('WebAdminLanguage'), 0, 2);
433
        $translates           = [];
434
        $extensionsTranslates = [[]];
435
        $results              = glob($this->moduleDir . '/{Messages}/en.php', GLOB_BRACE);
436
        foreach ($results as $path) {
437
            $langArr = require $path;
438
            if (is_array($langArr)) {
439
                $extensionsTranslates[] = $langArr;
440
            }
441
        }
442
        if ($extensionsTranslates !== [[]]) {
443
            $translates = array_merge($translates, ...$extensionsTranslates);
444
        }
445
        if ($language !== 'en') {
446
            $additionalTranslates = [[]];
447
            $results              = glob($this->moduleDir . "/{Messages}/{$language}.php", GLOB_BRACE);
448
            foreach ($results as $path) {
449
                $langArr = require $path;
450
                if (is_array($langArr)) {
451
                    $additionalTranslates[] = $langArr;
452
                }
453
            }
454
            if ($additionalTranslates !== [[]]) {
455
                $translates = array_merge($translates, ...$additionalTranslates);
456
            }
457
        }
458
459
        // Return a translation object
460
        if (array_key_exists($stringId, $translates)) {
461
            return $translates[$stringId];
462
        }
463
464
        return $stringId;
465
    }
466
467
    /**
468
     * Обходит файлы с описанием моделей и создает таблицы в базе данных
469
     *
470
     * @return bool
471
     */
472
    public function createSettingsTableByModelsAnnotations(): bool
473
    {
474
475
        // Add new connection for this module after add new Models folder
476
        RegisterDIServices::recreateModulesDBConnections();
477
478
        $results = glob($this->moduleDir . '/Models/*.php', GLOB_NOSORT);
479
        $dbUpgrade = new UpdateDatabase();
480
        foreach ($results as $file) {
481
            $className        = pathinfo($file)['filename'];
482
            $moduleModelClass = "\\Modules\\{$this->moduleUniqueID}\\Models\\{$className}";
483
            $upgradeResult = $dbUpgrade->createUpdateDbTableByAnnotations($moduleModelClass);
484
            if (!$upgradeResult){
485
                return false;
486
            }
487
488
        }
489
        // Update database connections after upgrade their structure
490
        RegisterDIServices::recreateModulesDBConnections();
491
492
        return true;
493
    }
494
495
496
    /**
497
     * Добавляет модуль в боковое меню
498
     *
499
     * @return bool
500
     */
501
    public function addToSidebar(): bool
502
    {
503
        $menuSettingsKey           = "AdditionalMenuItem{$this->moduleUniqueID}";
504
        $unCamelizedControllerName = Text::uncamelize($this->moduleUniqueID, '-');
505
        $menuSettings              = PbxSettings::findFirstByKey($menuSettingsKey);
506
        if ($menuSettings === null) {
507
            $menuSettings      = new PbxSettings();
508
            $menuSettings->key = $menuSettingsKey;
509
        }
510
        $value               = [
511
            'uniqid'        => $this->moduleUniqueID,
512
            'href'          => "/admin-cabinet/$unCamelizedControllerName",
513
            'group'         => 'maintenance',
514
            'iconClass'     => 'puzzle',
515
            'caption'       => "Breadcrumb$this->moduleUniqueID",
516
            'showAtSidebar' => true,
517
        ];
518
        $menuSettings->value = json_encode($value);
519
520
        return $menuSettings->save();
521
    }
522
}