Passed
Push — 2.x ( e707aa...d6bbff )
by Terry
02:22
created

BaseController::selected()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 2
dl 0
loc 6
rs 10
c 0
b 0
f 0
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
 * php version 7.1.0
11
 * 
12
 * @category  Web-security
13
 * @package   Shieldon
14
 * @author    Terry Lin <[email protected]>
15
 * @copyright 2019 terrylinooo
16
 * @license   https://github.com/terrylinooo/shieldon/blob/2.x/LICENSE MIT
17
 * @link      https://github.com/terrylinooo/shieldon
18
 * @see       https://shieldon.io
19
 */
20
21
declare(strict_types=1);
22
23
namespace Shieldon\Firewall\Panel;
24
25
use Psr\Http\Message\ResponseInterface;
26
use Shieldon\Firewall\Firewall;
27
use Shieldon\Firewall\FirewallTrait;
28
use Shieldon\Firewall\Panel\DemoModeTrait;
29
use Shieldon\Firewall\Panel\ConfigMethodsTrait;
30
use Shieldon\Firewall\Panel\CsrfTrait;
31
use Shieldon\Firewall\Utils\Container;
32
use Shieldon\Firewall\Log\ActionLogParser;
33
use function Shieldon\Firewall\__;
34
use function Shieldon\Firewall\get_request;
35
use function Shieldon\Firewall\get_response;
36
use function Shieldon\Firewall\get_session;
37
use function Shieldon\Firewall\unset_superglobal;
38
use function Shieldon\Firewall\get_user_lang;
39
40
use RuntimeException;
41
use function array_push;
42
use function define;
43
use function defined;
44
use function extract;
45
use function file_exists;
46
use function file_put_contents;
47
use function in_array;
48
use function is_array;
49
use function json_encode;
50
use function ob_end_clean;
51
use function ob_get_contents;
52
use function ob_start;
53
use function trim;
54
55
/**
56
 * Base controller.
57
 */
58
class BaseController
59
{
60
    use ConfigMethodsTrait;
61
    use CsrfTrait;
62
    use DemoModeTrait;
63
    use FirewallTrait;
64
65
    /**
66
     * LogPaeser instance.
67
     *
68
     * @var object
69
     */
70
    protected $parser;
71
72
    /**
73
     * Messages.
74
     *
75
     * @var array
76
     */
77
    protected $messages = [];
78
79
    /**
80
     * Check page availability.
81
     *
82
     * @var array
83
     */
84
    protected $pageAvailability = [
85
86
        // Need to implement Action Logger to make it true.
87
        'logs' => false,
88
    ];
89
90
    /**
91
     * Language code.
92
     *
93
     * @var string
94
     */
95
    protected $locate = 'en';
96
97
    /**
98
     * Captcha modules.
99
     *
100
     * @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...
101
     */
102
    protected $captcha = [];
103
104
    /**
105
     * The base URL of the firewall panel.
106
     *
107
     * @var string
108
     */
109
    public $base = '';
110
111
    /**
112
     * Firewall panel base controller.                  
113
     */
114
    public function __construct() 
115
    {
116
        $firewall = Container::get('firewall');
117
118
        if (!($firewall instanceof Firewall)) {
119
            throw new RuntimeException(
120
                'The Firewall instance should be initialized first.'
121
            );
122
        }
123
124
        $this->mode          = 'managed';
125
        $this->kernel        = $firewall->getKernel();
126
        $this->configuration = $firewall->getConfiguration();
127
        $this->directory     = $firewall->getDirectory();
128
        $this->filename      = $firewall->getFilename();
129
        $this->base          = SHIELDON_PANEL_BASE;
130
131
        if (!empty($this->kernel->logger)) {
132
133
            // We need to know where the logs stored in.
134
            $logDirectory = $this->kernel->logger->getDirectory();
135
136
            // Load ActionLogParser for parsing log files.
137
            $this->parser = new ActionLogParser($logDirectory);
138
139
            $this->pageAvailability['logs'] = true;
140
        }
141
142
        $flashMessage = get_session()->get('flash_messages');
143
144
        // Flash message, use it when redirecting page.
145
        if (!empty($flashMessage)) {
146
            $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...
147
            get_session()->remove('flash_messages');
148
        }
149
150
        $this->locate = get_user_lang();
151
    }
152
153
    /**
154
     * Load view file.
155
     *
156
     * @param string $page The page type. (filename)
157
     * @param array  $data The variables passed to that page.
158
     *
159
     * @return string
160
     */
161
    protected function loadView(string $page, array $data = []): string
162
    {
163
        if (!defined('SHIELDON_VIEW')) {
164
            define('SHIELDON_VIEW', true);
165
        }
166
167
        $viewFilePath =  __DIR__ . '/../../../templates/' . $page . '.php';
168
    
169
        if (!empty($data)) {
170
            extract($data);
171
        }
172
173
        $output = '';
174
    
175
        if (file_exists($viewFilePath)) {
176
            ob_start();
177
            include $viewFilePath;
178
            $output = ob_get_contents();
179
            ob_end_clean();
180
        }
181
182
        return $output;
183
    }
184
185
    /**
186
     * Render the web page with full layout.
187
     *
188
     * @param string $page The page type. (filename)
189
     * @param array  $data The variables passed to that page.
190
     *
191
     * @return ResponseInterface
192
     */
193
    protected function renderPage(string $page, array $data): ResponseInterface
194
    {
195
        $channelName = $this->kernel->driver->getChannel();
196
        $body = [];
197
198
        if (empty($channelName)) {
199
            $channelName = 'default';
200
        }
201
202
        $body['channel_name'] = $channelName;
203
        $body['mode_name'] = $this->mode;
204
        $body['page_url'] = $this->url();
205
        $body['content'] = $this->loadView($page, $data);
206
        $body['title'] = $data['title'] ?? '';
207
208
        $body['title'] .= ' - ' . __('panel', 'title_site_wide', 'Shieldon Firewall');
209
        $body['title'] .= ' v' . SHIELDON_FIREWALL_VERSION;
210
211
        $page = $this->loadView('panel/template', $body);
212
213
        return $this->respond($page);
214
    }
215
216
    /**
217
     * Return the response instance.
218
     *
219
     * @param string $body The content body.
220
     *
221
     * @return ResponseInterface
222
     */
223
    protected function respond(string $body): ResponseInterface
224
    {
225
        $response = get_response();
226
        $stream = $response->getBody();
227
        $stream->write($body);
228
        $stream->rewind();
229
230
        return $response->withBody($stream);
231
    }
232
233
    /**
234
     * Include a view file. This 
235
     * This method is used in a template loading other templates.
236
     *
237
     * @param string $page The page type. (filename)
238
     * @param array  $data The variables passed to that page.
239
     *
240
     * @return void
241
     */
242
    protected function loadViewPart(string $page, array $data = []): void
243
    {
244
        if (!defined('SHIELDON_VIEW')) {
245
            define('SHIELDON_VIEW', true);
246
        }
247
248
        foreach ($data as $k => $v) {
249
            ${$k} = $v;
250
        }
251
252
        include __DIR__ . '/../../../templates/' . $page . '.php';
253
    }
254
255
    /**
256
     * Response message to front.
257
     *
258
     * @param string $type The message status type. error|success
259
     * @param string $text The message body.
260
     *
261
     * @return void
262
     */
263
    protected function pushMessage(string $type, string $text): void
264
    {
265
        $class = $type;
266
267
        if ($type == 'error') {
268
            $class = 'danger';
269
        }
270
271
        array_push(
272
            $this->messages,
273
            [
274
                'type' => $type,
275
                'text' => $text,
276
                'class' => $class,
277
            ]
278
        );
279
    }
280
281
    /**
282
     * Return the relative URL.
283
     *
284
     * @param string $path The page's path.
285
     *
286
     * @return string
287
     */
288
    protected function url(string $path = ''): string
289
    {
290
        return '/' . trim($this->base, '/') . '/' . $path . '/';
291
    }
292
293
    /**
294
     * Save the configuration settings to the JSON file.
295
     *
296
     * @return void
297
     */
298
    protected function saveConfig(): void
299
    {
300
        if ($this->mode !== 'managed') {
301
            return;
302
        }
303
304
        $postParams = (array) get_request()->getParsedBody();
305
306
        $configFilePath = $this->directory . '/' . $this->filename;
307
308
        foreach ($this->csrfField as $csrfInfo) {
309
            if (!empty($csrfInfo['name'])) {
310
                unset_superglobal($csrfInfo['name'], 'post');
311
            }
312
        }
313
314
        $this->saveConfigPrepareSettings($postParams);
315
316
        //  Start checking the availibility of the data driver settings.
317
        $result = true;
318
        $result = $this->saveConfigCheckDataDriver($result);
319
        $result = $this->saveConfigCheckActionLogger($result);
320
        $result = $this->saveConfigCheckIptables($result);
321
322
        // Only update settings while data driver is correctly connected.
323
        if ($result) {
324
            file_put_contents($configFilePath, json_encode($this->configuration));
325
326
            $this->pushMessage(
327
                'success',
328
                __(
329
                    'panel',
330
                    'success_settings_saved',
331
                    'Settings saved.'
332
                )
333
            );
334
        }
335
    }
336
337
    /**
338
     * Echo the setting string to the template.
339
     *
340
     * @param string $field   Field.
341
     * @param mixed  $default Default value.
342
     *
343
     * @return void
344
     */
345
    protected function _(string $field, $default = ''): void
346
    {
347
        if ($this->mode === 'demo') {
348
349
            // Hide sensitive data because of security concerns.
350
            $hiddenForDemo = [
351
                'drivers.redis.auth',
352
                'drivers.file.directory_path',
353
                'drivers.sqlite.directory_path',
354
                'drivers.mysql.dbname',
355
                'drivers.mysql.user',
356
                'drivers.mysql.pass',
357
                'captcha_modules.recaptcha.config.site_key',
358
                'captcha_modules.recaptcha.config.secret_key',
359
                'loggers.action.config.directory_path',
360
                'admin.user',
361
                'admin.pass',
362
                'admin.last_modified',
363
                'messengers.telegram.config.api_key',
364
                'messengers.telegram.config.channel',
365
                'messengers.sendgrid.config.api_key',
366
                'messengers.sendgrid.config.sender',
367
                'messengers.sendgrid.config.recipients',
368
                'messengers.line_notify.config.access_token',
369
                'iptables.config.watching_folder',
370
                'ip6tables.config.watching_folder',
371
                'messengers.sendgrid.config.recipients', // array
372
            ];
373
374
            if (in_array($field, $hiddenForDemo)) {
375
                echo __('panel', 'field_not_visible', 'Cannot view this field in demo mode.');
376
                return;
377
            }
378
        }
379
380
        $fieldtype = gettype($this->getConfig($field));
381
382
        if (in_array($fieldtype, ['integer', 'string', 'double'])) {
383
            echo (!empty($this->getConfig($field))) ? $this->getConfig($field) : $default;
384
            return;
385
            // @codeCoverageIgnoreStart
386
        } elseif (in_array($fieldtype, ['array'])) {
387
            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

387
            echo implode("\n", /** @scrutinizer ignore-type */ $this->getConfig($field));
Loading history...
388
            return;
389
        }
390
391
        echo '';
392
393
        // @codeCoverageIgnoreEnd
394
    }
395
396
    /**
397
     * Use on HTML checkbox and radio elements.
398
     *
399
     * @param string $value        The variable or configuation field.
400
     * @param mixed  $valueChecked The value.
401
     * @param bool   $isConfig     Is it a configuration field or not.
402
     *
403
     * @return void
404
     */
405
    protected function checked(string $value, $valueChecked, bool $isConfig = true): void
406
    {
407
        if ($isConfig) {
408
            if ($this->getConfig($value) === $valueChecked) {
409
                echo 'checked';
410
                return;
411
            }
412
        } else {
413
            if ($value === $valueChecked) {
414
                echo 'checked';
415
                return;
416
            }
417
        }
418
419
        echo '';
420
    }
421
422
    /**
423
     * Echo correspondence string on Messenger setting page.
424
     *
425
     * @param string $moduleName The messenger module's name.
426
     * @param string $echoType   Value: css | icon
427
     *
428
     * @return void
429
     */
430
    protected function messengerAjaxStatus(string $moduleName, string $echoType = 'css'): void
431
    {
432
        $echo['css'] = $this->getConfig('messengers.' . $moduleName . '.confirm_test') ? 
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...
433
            'success' :
434
            '';
435
        
436
        $echo['icon'] = $this->getConfig('messengers.' . $moduleName . '.confirm_test') ?
437
            '<i class="fas fa-check"></i>' :
438
            '<i class="fas fa-exclamation"></i>';
439
440
        echo $echo[$echoType];
441
    }
442
}
443