Passed
Push — 2.x ( c47f49...8be9c2 )
by Terry
01:48
created

BaseController   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 411
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 147
dl 0
loc 411
rs 9.1199
c 4
b 0
f 0
wmc 41

13 Methods

Rating   Name   Duplication   Size   Complexity  
A respond() 0 8 1
A renderPage() 0 21 2
A _include() 0 11 3
A __construct() 0 37 4
A pushMessage() 0 12 2
A loadView() 0 22 4
A _m() 0 6 3
B saveConfig() 0 37 6
A url() 0 3 1
A checked() 0 15 4
A _csrf() 0 5 3
B _() 0 47 6
A selected() 0 6 2

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
 * This file is part of the Shieldon package.
4
 *
5
 * (c) Terry L. <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
declare(strict_types=1);
12
13
namespace Shieldon\Firewall\Panel;
14
15
use Psr\Http\Message\ResponseInterface;
16
use Shieldon\Firewall\Firewall;
17
use Shieldon\Firewall\FirewallTrait;
18
use Shieldon\Firewall\Panel\DemoModeTrait;
19
use Shieldon\Firewall\Panel\ConfigMethodsTrait;
20
use Shieldon\Firewall\Utils\Container;
21
use Shieldon\Firewall\Log\ActionLogParser;
22
use function Shieldon\Firewall\__;
23
use function Shieldon\Firewall\get_request;
24
use function Shieldon\Firewall\get_response;
25
use function Shieldon\Firewall\get_session;
26
use function Shieldon\Firewall\unset_superglobal;
27
use function Shieldon\Firewall\get_user_lang;
28
29
use RuntimeException;
30
use function array_push;
31
use function define;
32
use function defined;
33
use function extract;
34
use function file_exists;
35
use function file_put_contents;
36
use function is_array;
37
use function is_numeric;
38
use function is_string;
39
use function json_encode;
40
use function ob_end_clean;
41
use function ob_get_contents;
42
use function ob_start;
43
use function trim;
44
45
/**
46
 * User
47
 */
48
class BaseController
49
{
50
    use FirewallTrait;
51
    use DemoModeTrait;
52
    use ConfigMethodsTrait;
53
54
    /**
55
     * LogPaeser instance.
56
     *
57
     * @var object
58
     */
59
    protected $parser;
60
61
    /**
62
     * Messages.
63
     *
64
     * @var array
65
     */
66
    protected $messages = [];
67
68
    /**
69
     * Check page availability.
70
     *
71
     * @var array
72
     */
73
    protected $pageAvailability = [
74
75
        // Need to implement Action Logger to make it true.
76
        'logs' => false,
77
    ];
78
79
    /**
80
     * see $this->csrf()
81
     *
82
     * @var array
83
     */
84
    protected $csrfField = [];
85
86
    /**
87
     * Language code.
88
     *
89
     * @var string
90
     */
91
    protected $locate = 'en';
92
93
    /**
94
     * Captcha modules.
95
     *
96
     * @var Interface
0 ignored issues
show
Bug introduced by
The type Shieldon\Firewall\Panel\Interface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
97
     */
98
    protected $captcha = [];
99
100
    /**
101
     * The base URL of the firewall panel.
102
     *
103
     * @var string
104
     */
105
    public $base = '';
106
107
    /**
108
     * Firewall panel base controller.                  
109
     */
110
    public function __construct() 
111
    {
112
        $firewall = Container::get('firewall');
113
114
        if (!($firewall instanceof Firewall)) {
115
            throw new RuntimeException(
116
                'The Firewall instance should be initialized first.'
117
            );
118
        }
119
120
        $this->mode          = 'managed';
121
        $this->kernel        = $firewall->getKernel();
122
        $this->configuration = $firewall->getConfiguration();
123
        $this->directory     = $firewall->getDirectory();
124
        $this->filename      = $firewall->getFilename();
125
        $this->base          = SHIELDON_PANEL_BASE;
126
127
        if (!empty($this->kernel->logger)) {
128
129
            // We need to know where the logs stored in.
130
            $logDirectory = $this->kernel->logger->getDirectory();
131
132
            // Load ActionLogParser for parsing log files.
133
            $this->parser = new ActionLogParser($logDirectory);
134
135
            $this->pageAvailability['logs'] = true;
136
        }
137
138
        $flashMessage = get_session()->get('flash_messages');
139
140
        // Flash message, use it when redirecting page.
141
        if (!empty($flashMessage)) {
142
            $this->messages = $flashMessage;
0 ignored issues
show
Documentation Bug introduced by
It seems like $flashMessage can also be of type string. However, the property $messages is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
143
            get_session()->remove('flash_messages');
144
        }
145
146
        $this->locate = get_user_lang();
147
    }
148
149
    /**
150
     * Load view file.
151
     *
152
     * @param string $page The page type. (filename)
153
     * @param array  $data The variables passed to that page.
154
     *
155
     * @return string
156
     */
157
    protected function loadView(string $page, array $data = []): string
158
    {
159
        if (!defined('SHIELDON_VIEW')) {
160
            define('SHIELDON_VIEW', true);
161
        }
162
163
        $viewFilePath =  __DIR__ . '/../../../templates/' . $page . '.php';
164
    
165
        if (!empty($data)) {
166
            extract($data);
167
        }
168
169
        $output = '';
170
    
171
        if (file_exists($viewFilePath)) {
172
            ob_start();
173
            require $viewFilePath;
174
            $output = ob_get_contents();
175
            ob_end_clean();
176
        }
177
178
        return $output;
179
    }
180
181
    /**
182
     * Render the web page with full layout.
183
     *
184
     * @param string $page The page type. (filename)
185
     * @param array  $data The variables passed to that page.
186
     *
187
     * @return ResponseInterface
188
     */
189
    protected function renderPage(string $page, array $data): ResponseInterface
190
    {
191
        $channelName = $this->kernel->driver->getChannel();
192
        $body = [];
193
194
        if (empty($channelName)) {
195
            $channelName = 'default';
196
        }
197
198
        $body['channel_name'] = $channelName;
199
        $body['mode_name'] = $this->mode;
200
        $body['page_url'] = $this->url();
201
        $body['content'] = $this->loadView($page, $data);
202
        $body['title'] = $data['title'] ?? '';
203
204
        $body['title'] .= ' - ' . __('panel', 'title_site_wide', 'Shieldon Firewall');
205
        $body['title'] .= ' v' . SHIELDON_FIREWALL_VERSION;
206
207
        $page = $this->loadView('panel/template', $body);
208
209
        return $this->respond($page);
210
    }
211
212
    /**
213
     * Return the response instance.
214
     *
215
     * @param string $body The content body.
216
     *
217
     * @return ResponseInterface
218
     */
219
    protected function respond(string $body): ResponseInterface
220
    {
221
        $response = get_response();
222
        $stream = $response->getBody();
223
        $stream->write($body);
224
        $stream->rewind();
225
226
        return $response->withBody($stream);
227
    }
228
229
    /**
230
     * Include a view file.
231
     *
232
     * @param string $page The page type. (filename)
233
     * @param array  $data The variables passed to that page.
234
     *
235
     * @return void
236
     */
237
    protected function _include(string $page, array $data = []): void
238
    {
239
        if (!defined('SHIELDON_VIEW')) {
240
            define('SHIELDON_VIEW', true);
241
        }
242
243
        foreach ($data as $k => $v) {
244
            ${$k} = $v;
245
        }
246
247
        require __DIR__ . '/../../../templates/' . $page . '.php';
248
    }
249
250
    /**
251
     * Response message to front.
252
     *
253
     * @param string $type The message status type. error|success
254
     * @param string $text The message body.
255
     *
256
     * @return void
257
     */
258
    protected function pushMessage(string $type, string $text): void
259
    {
260
        $class = $type;
261
262
        if ($type == 'error') {
263
            $class = 'danger';
264
        }
265
266
        array_push($this->messages, [
267
            'type' => $type,
268
            'text' => $text,
269
            'class' => $class,
270
        ]);
271
    }
272
273
    /**
274
     * Return the relative URL.
275
     *
276
     * @param string $path The page's path.
277
     *
278
     * @return string
279
     */
280
    protected function url(string $path = ''): string
281
    {
282
        return '/' . trim($this->base, '/') . '/' . $path . '/';
283
    }
284
285
    /**
286
     * Output HTML input element with CSRF token.
287
     *
288
     * @return void
289
     */
290
    public function _csrf(): void
291
    {
292
        if (!empty($this->csrfField)) {
293
            foreach ($this->csrfField as $value) {
294
                echo '<input type="hidden" name="' . $value['name'] . '" value="' . $value['value'] . '" id="csrf-field">';
295
            }
296
        }
297
    }
298
299
    /**
300
     * Save the configuration settings to the JSON file.
301
     *
302
     * @return void
303
     */
304
    protected function saveConfig(): void
305
    {
306
        if ($this->mode !== 'managed') {
307
            return;
308
309
        }
310
        $postParams = get_request()->getParsedBody();
311
312
        if (!is_array($postParams)) {
313
            return;
314
        }
315
316
        $configFilePath = $this->directory . '/' . $this->filename;
317
318
        foreach ($this->csrfField as $csrfInfo) {
319
            if (!empty($csrfInfo['name'])) {
320
                unset_superglobal($csrfInfo['name'], 'post');
321
            }
322
        }
323
324
        $this->saveConfigPrepareSettings($postParams);
325
326
        //  Start checking the availibility of the data driver settings.
327
        $result = true;
328
        $result = $this->saveConfigCheckDataDriver($result);
329
        $result = $this->saveConfigCheckActionLogger($result);
330
        $result = $this->saveConfigCheckIptables($result);
331
332
        // Only update settings while data driver is correctly connected.
333
        if ($result) {
334
            file_put_contents($configFilePath, json_encode($this->configuration));
335
336
            $this->pushMessage('success',
337
                __(
338
                    'panel',
339
                    'success_settings_saved',
340
                    'Settings saved.'
341
                )
342
            );
343
        }
344
    }
345
346
    /**
347
     * Echo the setting string to the template.
348
     *
349
     * @param string $field   Field.
350
     * @param mixed  $defailt Default value.
351
     *
352
     * @return void
353
     */
354
    protected function _(string $field, $default = ''): void
355
    {
356
        if ('demo' === $this->mode) {
357
358
            // Hide sensitive data because of security concerns.
359
            $hiddenForDemo = [
360
                'drivers.redis.auth',
361
                'drivers.file.directory_path',
362
                'drivers.sqlite.directory_path',
363
                'drivers.mysql.dbname',
364
                'drivers.mysql.user',
365
                'drivers.mysql.pass',
366
                'captcha_modules.recaptcha.config.site_key',
367
                'captcha_modules.recaptcha.config.secret_key',
368
                'loggers.action.config.directory_path',
369
                'admin.user',
370
                'admin.pass',
371
                'admin.last_modified',
372
                'messengers.telegram.config.api_key',
373
                'messengers.telegram.config.channel',
374
                'messengers.sendgrid.config.api_key',
375
                'messengers.sendgrid.config.sender',
376
                'messengers.sendgrid.config.recipients',
377
                'messengers.line_notify.config.access_token',
378
                'iptables.config.watching_folder',
379
                'ip6tables.config.watching_folder',
380
                'messengers.sendgrid.config.recipients', // array
381
            ];
382
383
            if (in_array($field, $hiddenForDemo)) {
384
                echo __('panel', 'field_not_visible', 'Cannot view this field in demo mode.');
385
                return;
386
            }
387
        }
388
389
        $fieldtype = gettype($this->getConfig($field));
390
391
        if (in_array($fieldtype, ['integer', 'string', 'double'])) {
392
            echo (!empty($this->getConfig($field))) ? $this->getConfig($field) : $default;
393
            return;
394
395
        } elseif (in_array($fieldtype, ['array'])) {
396
            echo implode("\n", $this->getConfig($field));
0 ignored issues
show
Bug introduced by
It seems like $this->getConfig($field) can also be of type string; however, parameter $pieces of implode() does only seem to accept array, 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

396
            echo implode("\n", /** @scrutinizer ignore-type */ $this->getConfig($field));
Loading history...
397
            return;
398
        }
399
400
        echo '';
401
    }
402
403
    /**
404
     * Use on HTML checkbox and radio elements.
405
     *
406
     * @param string $value        The variable or configuation field.
407
     * @param mixed  $valueChecked The value.
408
     * @param bool   $isConfig     Is it a configuration field or not.
409
     *
410
     * @return void
411
     */
412
    protected function checked(string $value, $valueChecked, bool $isConfig = true): void
413
    {
414
        if ($isConfig) {
415
            if ($this->getConfig($value) === $valueChecked) {
416
                echo 'checked';
417
                return;
418
            }
419
        } else {
420
            if ($value === $valueChecked) {
421
                echo 'checked';
422
                return;
423
            }
424
        }
425
426
        echo '';
427
    }
428
429
    /**
430
     * Echo correspondence string on Messenger setting page.
431
     *
432
     * @param string $moduleName The messenger module's name.
433
     * @param string $echoType   Value: css | icon
434
     *
435
     * @return void
436
     */
437
    protected function _m(string $moduleName, string $echoType = 'css'): void
438
    {
439
        $echo['css'] = $this->getConfig('messengers.' . $moduleName . '.confirm_test') ? 'success' : '';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$echo was never initialized. Although not strictly required by PHP, it is generally a good practice to add $echo = array(); before regardless.
Loading history...
440
        $echo['icon'] = $this->getConfig('messengers.' . $moduleName . '.confirm_test') ? '<i class="fas fa-check"></i>' : '<i class="fas fa-exclamation"></i>';
441
442
        echo $echo[$echoType];
443
    }
444
445
    /**
446
     * Use on HTML select elemets.
447
     *
448
     * @param string $value
449
     * @param mixed  $valueChecked
450
     *
451
     * @return void
452
     */
453
    protected function selected(string $value, $valueChecked): void
454
    {
455
        if ($this->getConfig($value) === $valueChecked) {
456
            echo 'selected';
457
        } else {
458
            echo '';
459
        }
460
    }
461
}
462
463