Passed
Push — develop ( ba81f1...650c64 )
by Портнов
11:27
created

BaseController   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 326
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 56
eloc 167
c 4
b 0
f 0
dl 0
loc 326
rs 5.5199

11 Methods

Rating   Name   Duplication   Size   Complexity  
A initialize() 0 10 2
B afterExecuteRoute() 0 28 8
A beforeExecuteRoute() 0 6 3
A sanitizeCallerId() 0 3 1
A getVersionsHash() 0 9 2
A getSessionData() 0 11 4
A forward() 0 10 1
A sortArrayByPriority() 0 18 5
F prepareView() 0 91 18
B customWikiLinks() 0 47 11
A customModuleWikiLinks() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like BaseController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BaseController, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright (C) 2017-2020 Alexey Portnov and Nikolay Beketov
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with this program.
17
 * If not, see <https://www.gnu.org/licenses/>.
18
 */
19
20
namespace MikoPBX\AdminCabinet\Controllers;
21
22
use MikoPBX\Common\Providers\ModelsCacheProvider;
23
use MikoPBX\Core\System\Util;
24
use MikoPBX\Common\Models\{PbxExtensionModules, PbxSettings};
25
use Phalcon\Mvc\{Controller, View};
26
use Phalcon\Cache\Adapter\Redis;
27
use Phalcon\Tag;
28
use Phalcon\Text;
29
use Sentry\SentrySdk;
30
use GuzzleHttp;
31
use Exception;
32
33
/**
34
 * @property array                                         sessionRO
35
 * @property \Phalcon\Session\Manager                      session
36
 * @property \MikoPBX\Common\Providers\TranslationProvider translation
37
 * @property string                                        language
38
 * @property \MikoPBX\AdminCabinet\Library\Elements        elements
39
 * @property string                                        moduleName
40
 * @property \Phalcon\Flash\Session                        flash
41
 * @property \Phalcon\Tag                                  tag
42
 * @property \Phalcon\Config\Adapter\Json                  config
43
 * @property \Phalcon\Logger                                loggerAuth
44
 */
45
class BaseController extends Controller
46
{
47
    protected string $actionName;
48
    protected string $controllerName;
49
    protected string $controllerNameUnCamelized;
50
51
    public const WIKI_LINKS = '/var/etc/wiki-links-LANG.json';
52
53
    /**
54
     * Initializes base class
55
     */
56
    public function initialize(): void
57
    {
58
        $this->actionName                = $this->dispatcher->getActionName();
59
        $this->controllerName            = Text::camelize($this->dispatcher->getControllerName(), '_');
60
        $this->controllerNameUnCamelized = Text::uncamelize($this->controllerName, '-');
61
62
        $this->moduleName = $this->dispatcher->getModuleName();
63
64
        if ($this->request->isAjax() === false) {
65
            $this->prepareView();
66
        }
67
    }
68
69
    /**
70
     * Кастомизация ссылок на wiki документацию для модулей.
71
     * @param array $links
72
     * @return void
73
     */
74
    private function customModuleWikiLinks(array $links): void
75
    {
76
        $this->view->urlToWiki    = $links[$this->language][$this->view->urlToWiki]??$this->view->urlToWiki;
77
        $this->view->urlToSupport = $links[$this->language][$this->view->urlToSupport]??$this->view->urlToSupport;
78
    }
79
80
    /**
81
     * Кастомизация ссылок на wiki документацию.
82
     * @return void
83
     */
84
    private function customWikiLinks(): void
85
    {
86
        if(!$this->session->get('auth')){
87
            return;
88
        }
89
        /** @var Redis $cache */
90
        $cache  = $this->di->getShared(ModelsCacheProvider::SERVICE_NAME);
91
        $links  = $cache->get('WIKI_LINKS');
92
93
        if($links === null){
94
            $ttl = 86400;
95
            $client = new GuzzleHttp\Client();
96
            $url = 'https://raw.githubusercontent.com/mikopbx/Core/master/src/Common/WikiLinks/'.$this->language.'.json';
97
            try {
98
                $res = $client->request('GET', $url, ['timeout', 1]);
99
            }catch (Exception $e){
100
                $res = null;
101
                $ttl = 3600;
102
                if($e->getCode() !== 404){
103
                    Util::sysLogMsg('BaseController', 'Error access to raw.04githubusercontent.com');
104
                }
105
            }
106
            $links = null;
107
            if($res && $res->getStatusCode() === 200){
108
                try {
109
                    $links = json_decode($res->getBody(), true, 512, JSON_THROW_ON_ERROR);
110
                }catch (Exception $e){
111
                    $ttl = 3600;
112
                }
113
            }
114
            if(!is_array($links)){
115
                $links = [];
116
            }
117
            $cache->set('WIKI_LINKS', $links, $ttl);
118
        }
119
120
        $filename = str_replace('LANG', $this->language, self::WIKI_LINKS);
121
        if(file_exists($filename)){
122
            try {
123
                $local_links = json_decode(file_get_contents($filename), true, 512, JSON_THROW_ON_ERROR);
124
                $links = $local_links;
125
            }catch (\Exception $e){
126
                Util::sysLogMsg('BaseController', $e->getMessage());
127
            }
128
        }
129
        $this->view->urlToWiki    = $links[$this->view->urlToWiki]??$this->view->urlToWiki;
130
        $this->view->urlToSupport = $links[$this->view->urlToSupport]??$this->view->urlToSupport;
131
    }
132
133
    /**
134
     * Prepares some environments to every controller and view
135
     *
136
     */
137
    protected function prepareView(): void
138
    {
139
        date_default_timezone_set($this->getSessionData('PBXTimezone'));
140
        $roSession              = $this->sessionRO;
141
        $this->view->PBXVersion = $this->getSessionData('PBXVersion');
142
        if ($roSession !== null && array_key_exists('auth', $roSession)) {
143
            $this->view->SSHPort    = $this->getSessionData('SSHPort');
144
            $this->view->PBXLicense = $this->getSessionData('PBXLicense');
145
        } else {
146
            $this->view->SSHPort    = '';
147
            $this->view->PBXLicense = '';
148
        }
149
        // Кеш версий модулей и атс, для правильной работы АТС при установке модулей
150
        $versionHash = $this->getVersionsHash();
151
        $this->session->set('versionHash', $versionHash);
152
153
        $this->view->WebAdminLanguage   = $this->getSessionData('WebAdminLanguage');
154
        $this->view->AvailableLanguages = json_encode($this->elements->getAvailableWebAdminLanguages());
155
156
        if ($roSession !== null && array_key_exists('SubmitMode', $roSession)) {
157
            $this->view->submitMode = $roSession['SubmitMode'];
158
        } else {
159
            $this->view->submitMode = 'SaveSettings';
160
        }
161
162
        // Добавим версию модуля, если это модуль
163
        $moduleLinks = [];
164
        if ($this->moduleName === 'PBXExtension') {
165
            /** @var PbxExtensionModules $module */
166
            $module = PbxExtensionModules::findFirstByUniqid($this->controllerName);
167
            if ($module === null) {
168
                $module           = new PbxExtensionModules();
169
                $module->disabled = '1';
170
                $module->name     = 'Unknown module';
171
            }else{
172
                try {
173
                    $links = json_decode($module->wiki_links, true, 512, JSON_THROW_ON_ERROR);
0 ignored issues
show
Bug introduced by
It seems like $module->wiki_links can also be of type null; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

173
                    $links = json_decode(/** @scrutinizer ignore-type */ $module->wiki_links, true, 512, JSON_THROW_ON_ERROR);
Loading history...
174
                    if(is_array($links)){
175
                        $moduleLinks = $links;
176
                    }
177
                }catch (\JsonException $e){
178
                    Util::sysLogMsg(__CLASS__, $e->getMessage());
179
                }
180
            }
181
            $this->view->module = $module;
182
        }
183
184
        // Разрешим отправку анонимной информации об ошибках
185
        if ($this->getSessionData('SendMetrics') === '1') {
186
            touch('/tmp/sendmetrics');
187
            $this->view->lastSentryEventId = SentrySdk::getCurrentHub()->getLastEventId();
188
        } else {
189
            if (file_exists('/tmp/sendmetrics')) {
190
                unlink('/tmp/sendmetrics');
191
            }
192
            $this->view->lastSentryEventId = null;
193
        }
194
        $title = 'MikoPBX';
195
        switch ($this->actionName) {
196
            case'index':
197
            case'delete':
198
            case'save':
199
            case'modify':
200
            case'*** WITHOUT ACTION ***':
201
                $title .= '|'. $this->translation->_("Breadcrumb{$this->controllerName}");
202
                break;
203
            default:
204
                $title .= '|'. $this->translation->_("Breadcrumb{$this->controllerName}{$this->actionName}");
205
        }
206
        Tag::setTitle($title);
207
        $this->view->t         = $this->translation;
208
        $this->view->debugMode = $this->config->path('adminApplication.debugMode');
209
        $this->view->urlToLogo = $this->url->get('assets/img/logo-mikopbx.svg');
210
        if ($this->language === 'ru') {
211
            $this->view->urlToWiki    = "https://wiki.mikopbx.com/{$this->controllerNameUnCamelized}";
212
            $this->view->urlToSupport = 'https://www.mikopbx.ru/support/?fromPBX=true';
213
        } else {
214
            $this->view->urlToWiki    = "https://wiki.mikopbx.com/{$this->language}:{$this->controllerNameUnCamelized}";
215
            $this->view->urlToSupport = 'https://www.mikopbx.com/support/?fromPBX=true';
216
        }
217
        $this->view->urlToController = $this->url->get($this->controllerNameUnCamelized);
218
        $this->view->represent       = '';
219
        $this->view->cacheName       = "{$this->controllerName}{$this->actionName}{$this->language}{$versionHash}";
220
221
        // If it is module we have to use another template
222
        if ($this->moduleName === 'PBXExtension') {
223
            $this->customModuleWikiLinks($moduleLinks);
224
            $this->view->setTemplateAfter('modules');
225
        } else {
226
            $this->customWikiLinks();
227
            $this->view->setTemplateAfter('main');
228
        }
229
    }
230
231
    /**
232
     * Gets data from session or database if it not exists in session store
233
     *
234
     * @param $key string session parameter
235
     *
236
     * @return string
237
     */
238
    protected function getSessionData(string $key): string
239
    {
240
        $roSession = $this->sessionRO;
241
        if ($roSession !== null && array_key_exists($key, $roSession) && ! empty($roSession[$key])) {
242
            $value = $roSession[$key];
243
        } else {
244
            $value = PbxSettings::getValueByKey($key);
245
            $this->session->set($key, $value);
246
        }
247
248
        return $value;
249
    }
250
251
    /**
252
     * Generates common hash sum for correct combine CSS and JS according to installed modules
253
     *
254
     */
255
    private function getVersionsHash(): string
256
    {
257
        $result          = PbxSettings::getValueByKey('PBXVersion');
258
        $modulesVersions = PbxExtensionModules::getModulesArray();
259
        foreach ($modulesVersions as $module) {
260
            $result .= "{$module['id']}{$module['version']}";
261
        }
262
263
        return md5($result);
264
    }
265
266
    /**
267
     * Changes the AJAX response by expected format
268
     *
269
     * @return \Phalcon\Http\Response|\Phalcon\Http\ResponseInterface
270
     */
271
    public function afterExecuteRoute()
272
    {
273
        if ($this->request->isAjax() === true) {
274
            $this->view->setRenderLevel(View::LEVEL_NO_RENDER);
275
            $this->response->setContentType('application/json', 'UTF-8');
276
            $data = $this->view->getParamsToView();
277
278
            /* Set global params if is not set in controller/action */
279
            if (is_array($data) && isset($data['raw_response'])) {
280
                $result = $data['raw_response'];
281
            } elseif (is_array($data)) {
282
                $data['success'] = array_key_exists('success', $data) ? $data['success'] : true;
283
                $data['reload']  = array_key_exists('reload', $data) ? $data['reload'] : false;
284
                $data['message'] = $data['message'] ?? $this->flash->getMessages();
285
286
                // Добавим информацию о последней ошибке для отображения диалогового окна для пользователя
287
                if (file_exists('/tmp/sendmetrics')) {
288
                    $data['lastSentryEventId'] = SentrySdk::getCurrentHub()->getLastEventId();
289
                }
290
                $result = json_encode($data);
291
            } else {
292
                $result = '';
293
            }
294
295
            $this->response->setContent($result);
296
        }
297
298
        return $this->response->send();
299
    }
300
301
    /**
302
     * Callback before execute any route
303
     */
304
    public function beforeExecuteRoute(): void
305
    {
306
        if ($this->request->isPost()) {
307
            $data = $this->request->getPost('submitMode');
308
            if ( ! empty($data)) {
309
                $this->session->set('SubmitMode', $data);
310
            }
311
        }
312
    }
313
314
    /**
315
     * Change page without reload browser page
316
     *
317
     * @param string $uri
318
     */
319
    protected function forward(string $uri): void
320
    {
321
        $uriParts = explode('/', $uri);
322
        $params   = array_slice($uriParts, 2);
323
324
        $this->dispatcher->forward(
325
            [
326
                'controller' => $uriParts[0],
327
                'action'     => $uriParts[1],
328
                'params'     => $params,
329
            ]
330
331
        );
332
    }
333
334
    /**
335
     * Removes all dangerous symbols from CallerID
336
     * @param string $callerId
337
     *
338
     * @return string
339
     */
340
    protected function sanitizeCallerId(string $callerId): string
341
    {
342
        return preg_replace('/[^a-zA-Zа-яА-Я0-9 ]/ui', '', $callerId);
343
    }
344
345
    /**
346
     * Sorts array by priority field
347
     *
348
     * @param $a
349
     * @param $b
350
     *
351
     * @return int|null
352
     */
353
    protected function sortArrayByPriority($a, $b): ?int
354
    {
355
        if (is_array($a)){
356
            $a = (int)$a['priority'];
357
        } else {
358
            $a = (int)$a->priority;
359
        }
360
361
        if (is_array($b)){
362
            $b = (int)$b['priority'];
363
        } else {
364
            $b = (int)$b->priority;
365
        }
366
367
        if ($a === $b) {
368
            return 0;
369
        } else {
370
            return ($a < $b) ? -1 : 1;
371
        }
372
    }
373
}
374