Passed
Push — 2.x ( 3181da...fa7f46 )
by Terry
01:56
created

Kernel::setClosure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 1
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;
24
25
use Psr\Http\Message\ResponseInterface;
26
use Psr\Http\Message\ServerRequestInterface;
27
use Shieldon\Firewall\Captcha\Foundation;
28
use Shieldon\Firewall\Helpers;
29
use Shieldon\Firewall\HttpFactory;
30
use Shieldon\Firewall\IpTrait;
31
use Shieldon\Firewall\Kernel\CaptchaTrait;
32
use Shieldon\Firewall\Kernel\ComponentTrait;
33
use Shieldon\Firewall\Kernel\DriverTrait;
34
use Shieldon\Firewall\Kernel\FilterTrait;
35
use Shieldon\Firewall\Kernel\MessengerTrait;
36
use Shieldon\Firewall\Kernel\RuleTrait;
37
use Shieldon\Firewall\Kernel\SessionTrait;
38
use Shieldon\Firewall\Log\ActionLogger;
39
use Shieldon\Firewall\Utils\Container;
40
use function Shieldon\Firewall\get_default_properties;
41
use function Shieldon\Firewall\get_request;
42
use function Shieldon\Firewall\get_response;
43
use function Shieldon\Firewall\get_session;
44
45
use Closure;
46
use InvalidArgumentException;
47
use RuntimeException;
48
use function array_push;
49
use function file_exists;
50
use function get_class;
51
use function gethostbyaddr;
52
use function is_dir;
53
use function ltrim;
54
use function ob_end_clean;
55
use function ob_get_contents;
56
use function ob_start;
57
use function strpos;
58
use function strrpos;
59
use function substr;
60
use function time;
61
62
/**
63
 * The primary Shiendon class.
64
 */
65
class Kernel
66
{
67
    /**
68
     *   Public methods       | Desctiotion
69
     *  ----------------------|---------------------------------------------
70
     *   ban                  | Ban an IP.
71
     *   getCurrentUrl        | Get current user's browsing path.
72
     *   getJavascript        | Print a JavaScript snippet in the pages.
73
     *   managedBy            | Used on testing purpose.
74
     *   respond              | Respond the result.
75
     *   run                  | Run the checking process.
76
     *   setClosure           | Set a closure function.
77
     *   setDialog            | Customize the dialog UI.
78
     *   exclude              | Set a URL you want them excluded them from protection.
79
     *   setExcludedList      | Set the URLs you want them excluded them from protection.
80
     *   setLogger            | Set the action log logger.
81
     *   setProperties        | Set the property settings.
82
     *   setProperty          | Set a property setting.
83
     *   setStrict            | Strict mode apply to all components.
84
     *   setTemplateDirectory | Set the frontend template directory.
85
     *   unban                | Unban an IP.
86
     *  ----------------------|---------------------------------------------
87
     */
88
89
    /**
90
     *   Public methods       | Desctiotion
91
     *  ----------------------|---------------------------------------------
92
     *   setIp                | Ban an IP.
93
     *   getIp                | Get current user's browsing path.
94
     *   setRdns              | Print a JavaScript snippet in the pages.
95
     *   getRdns              | Used on testing purpose.
96
     *  ----------------------|---------------------------------------------
97
     */
98
    use CaptchaTrait;
99
100
    /**
101
     *   Public methods       | Desctiotion
102
     *  ----------------------|---------------------------------------------
103
     *   setComponent         | Set a commponent.
104
     *   getComponent         | Get a component instance from component's container.
105
     *   disableComponents    | Disable all components.
106
     *  ----------------------|---------------------------------------------
107
     */
108
    use ComponentTrait;
109
110
    /**
111
     *   Public methods       | Desctiotion
112
     *  ----------------------|---------------------------------------------
113
     *   setDriver            | Set a data driver.
114
     *   setChannel           | Set a data channel.
115
     *   disableDbBuilder     | disable creating data tables.
116
     *  ----------------------|---------------------------------------------
117
     */
118
    use DriverTrait;
119
120
    /**
121
     *   Public methods       | Desctiotion
122
     *  ----------------------|---------------------------------------------
123
     *   setFilters           | Set the filters.
124
     *   setFilter            | Set a filter.
125
     *   disableFilters       | Disable all filters.
126
     *  ----------------------|---------------------------------------------
127
     */
128
    use FilterTrait;
129
130
    /**
131
     *   Public methods       | Desctiotion
132
     *  ----------------------|---------------------------------------------
133
     *   setIp                | Set an IP address.
134
     *   getIp                | Get current set IP.
135
     *   setRdns              | Set a RDNS record for the check.
136
     *   getRdns              | Get IP resolved hostname.
137
     *  ----------------------|---------------------------------------------
138
     */
139
    use IpTrait;
140
141
    /**
142
     *   Public methods       | Desctiotion
143
     *  ----------------------|---------------------------------------------
144
     *   setMessenger         | Set a messenger
145
     *  ----------------------|---------------------------------------------
146
     */
147
    use MessengerTrait;
148
149
    /**
150
     *   Public methods       | Desctiotion
151
     *  ----------------------|---------------------------------------------
152
     *                        |  
153
     *  ----------------------|---------------------------------------------
154
     */
155
    use RuleTrait;
156
157
    /**
158
     *   Public methods       | Desctiotion
159
     *  ----------------------|---------------------------------------------
160
     *   limitSession         | Limit the amount of the online users.
161
     *   getSessionCount      | Get the amount of the sessions.
162
     *  ----------------------|---------------------------------------------
163
     */
164
    use SessionTrait;
165
166
    /**
167
     * HTTP Status Codes
168
     */
169
    const HTTP_STATUS_OK                 = 200;
170
    const HTTP_STATUS_SEE_OTHER          = 303;
171
    const HTTP_STATUS_BAD_REQUEST        = 400;
172
    const HTTP_STATUS_FORBIDDEN          = 403;
173
    const HTTP_STATUS_TOO_MANY_REQUESTS  = 429;
174
175
    /**
176
     * Reason Codes (ALLOW)
177
     */
178
    const REASON_IS_SEARCH_ENGINE        = 100;
179
    const REASON_IS_GOOGLE               = 101;
180
    const REASON_IS_BING                 = 102;
181
    const REASON_IS_YAHOO                = 103;
182
    const REASON_IS_SOCIAL_NETWORK       = 110;
183
    const REASON_IS_FACEBOOK             = 111;
184
    const REASON_IS_TWITTER              = 112;
185
186
    /**
187
     * Reason Codes (DENY)
188
     */
189
    const REASON_TOO_MANY_SESSIONS       = 1;
190
    const REASON_TOO_MANY_ACCESSES       = 2; // (not used)
191
    const REASON_EMPTY_JS_COOKIE         = 3;
192
    const REASON_EMPTY_REFERER           = 4;
193
    const REASON_REACHED_LIMIT_DAY       = 11;
194
    const REASON_REACHED_LIMIT_HOUR      = 12;
195
    const REASON_REACHED_LIMIT_MINUTE    = 13;
196
    const REASON_REACHED_LIMIT_SECOND    = 14;
197
    const REASON_INVALID_IP              = 40;
198
    const REASON_DENY_IP                 = 41;
199
    const REASON_ALLOW_IP                = 42;
200
    const REASON_COMPONENT_IP            = 81;
201
    const REASON_COMPONENT_RDNS          = 82;
202
    const REASON_COMPONENT_HEADER        = 83;
203
    const REASON_COMPONENT_USERAGENT     = 84;
204
    const REASON_COMPONENT_TRUSTED_ROBOT = 85;
205
    const REASON_MANUAL_BAN              = 99;
206
207
    /**
208
     * Action Codes
209
     */
210
    const ACTION_DENY                    = 0;
211
    const ACTION_ALLOW                   = 1;
212
    const ACTION_TEMPORARILY_DENY        = 2;
213
    const ACTION_UNBAN                   = 9;
214
215
    /**
216
     * Result Codes
217
     */
218
    const RESPONSE_DENY                  = 0;
219
    const RESPONSE_ALLOW                 = 1;
220
    const RESPONSE_TEMPORARILY_DENY      = 2;
221
    const RESPONSE_LIMIT_SESSION         = 3;
222
223
    /**
224
     * Logger Codes
225
     */
226
    const LOG_LIMIT                      = 3;
227
    const LOG_PAGEVIEW                   = 11;
228
    const LOG_BLACKLIST                  = 98;
229
    const LOG_CAPTCHA                    = 99;
230
231
    const KERNEL_DIR = __DIR__;
232
233
    /**
234
     * The result passed from filters, compoents, etc.
235
     * 
236
     * DENY    : 0
237
     * ALLOW   : 1
238
     * CAPTCHA : 2
239
     *
240
     * @var int
241
     */
242
    protected $result = 1;
243
244
    /**
245
     * Default settings
246
     *
247
     * @var array
248
     */
249
    protected $properties = [];
250
251
    /**
252
     * Logger instance.
253
     *
254
     * @var ActionLogger
255
     */
256
    public $logger;
257
258
    /**
259
     * The closure functions that will be executed in this->run()
260
     *
261
     * @var array
262
     */
263
    protected $closures = [];
264
265
    /**
266
     * URLs that are excluded from Shieldon's protection.
267
     *
268
     * @var array
269
     */
270
    protected $excludedUrls = [];
271
272
    /**
273
     * Custom dialog UI settings.
274
     *
275
     * @var array
276
     */
277
    protected $dialog = [];
278
279
    /**
280
     * Strict mode.
281
     * 
282
     * Set by `strictMode()` only. The default value of this propertry is undefined.
283
     *
284
     * @var bool|null
285
     */
286
    protected $strictMode;
287
288
    /**
289
     * The directory in where the frontend template files are placed.
290
     *
291
     * @var string
292
     */
293
    protected $templateDirectory = '';
294
295
    /**
296
     * Which type of configuration source that Shieldon firewall managed?
297
     * value: managed | config | self | demo
298
     *
299
     * @var string
300
     */
301
    protected $firewallType = 'self'; 
302
303
    /**
304
     * Shieldon constructor.
305
     *
306
     * @param ServerRequestInterface|null $request  A PSR-7 server request.
307
     * @param ResponseInterface|null      $response A PSR-7 server response.
308
     *
309
     * @return void
310
     */
311
    public function __construct(?ServerRequestInterface $request = null, ?ResponseInterface $response = null)
312
    {
313
        // Load helper functions. This is the must.
314
        new Helpers();
315
316
        $request = $request ?? HttpFactory::createRequest();
317
        $response = $response ?? HttpFactory::createResponse();
318
        $session = HttpFactory::createSession();
319
320
        $this->properties = get_default_properties();
321
        $this->setCaptcha(new Foundation());
322
323
        Container::set('request', $request);
324
        Container::set('response', $response);
325
        Container::set('session', $session);
326
        Container::set('shieldon', $this);
327
    }
328
329
    /**
330
     * Run, run, run!
331
     *
332
     * Check the rule tables first, if an IP address has been listed.
333
     * Call function filter() if an IP address is not listed in rule tables.
334
     *
335
     * @return int
336
     */
337
    public function run(): int
338
    {
339
        $this->assertDriver();
340
341
        // Ignore the excluded urls.
342
        foreach ($this->excludedUrls as $url) {
343
            if (strpos($this->getCurrentUrl(), $url) === 0) {
344
                return $this->result = self::RESPONSE_ALLOW;
345
            }
346
        }
347
348
        // Execute closure functions.
349
        foreach ($this->closures as $closure) {
350
            $closure();
351
        }
352
353
        $result = $this->process();
354
355
        if ($result !== self::RESPONSE_ALLOW) {
356
357
            // Current session did not pass the CAPTCHA, it is still stuck in 
358
            // CAPTCHA page.
359
            $actionCode = self::LOG_CAPTCHA;
360
361
            // If current session's respone code is RESPONSE_DENY, record it as 
362
            // `blacklist_count` in our logs.
363
            // It is stuck in warning page, not CAPTCHA.
364
            if ($result === self::RESPONSE_DENY) {
365
                $actionCode = self::LOG_BLACKLIST;
366
            }
367
368
            if ($result === self::RESPONSE_LIMIT_SESSION) {
369
                $actionCode = self::LOG_LIMIT;
370
            }
371
372
            $this->log($actionCode);
373
374
        } else {
375
376
            $this->log(self::LOG_PAGEVIEW);
377
        }
378
379
        // @ MessengerTrait
380
        $this->triggerMessengers();
381
382
        return $result;
383
    }
384
385
    /**
386
     * Respond the result.
387
     *
388
     * @return ResponseInterface
389
     */
390
    public function respond(): ResponseInterface
391
    {
392
        $response = get_response();
393
394
        $httpStatusCodes = [
395
            self::RESPONSE_TEMPORARILY_DENY => [
396
                'type' => 'captcha',
397
                'code' => self::HTTP_STATUS_FORBIDDEN,
398
            ],
399
400
            self::RESPONSE_LIMIT_SESSION => [
401
                'type' => 'session_limitation',
402
                'code' => self::HTTP_STATUS_TOO_MANY_REQUESTS,
403
            ],
404
405
            self::RESPONSE_DENY => [
406
                'type' => 'rejection',
407
                'code' => self::HTTP_STATUS_BAD_REQUEST,
408
            ],
409
        ];
410
411
        // Nothing happened. Return.
412
        if (empty($httpStatusCodes[$this->result])) {
413
            return $response;
414
        }
415
416
        $type = $httpStatusCodes[$this->result]['type'];
417
        $statusCode = $httpStatusCodes[$this->result]['code'];
418
419
        $viewPath = $this->getTemplate($type);
420
421
        // The language of output UI. It is used on views.
422
        $langCode = get_session()->get('shieldon_ui_lang') ?? 'en';
423
424
        $showOnlineInformation = false;
425
        $showUserInformation = false;
426
        
427
        // Show online session count. It is used on views.
428
        if (!empty($this->properties['display_online_info'])) {
429
            $showOnlineInformation = true;
430
            $onlineinfo = [];
431
432
            $onlineinfo['queue'] = $this->sessionStatus['queue'];
433
            $onlineinfo['count'] = $this->sessionStatus['count'];
434
            $onlineinfo['period'] = $this->sessionLimit['period'];
435
        } 
436
437
        // Show user information such as IP, user-agent, device name.
438
        if (!empty($this->properties['display_user_info'])) {
439
            $showUserInformation = true;
440
            $dialoguserinfo = [];
441
442
            $dialoguserinfo['ip'] = $this->ip;
443
            $dialoguserinfo['rdns'] = $this->rdns;
444
            $dialoguserinfo['user_agent'] = get_request()->getHeaderLine('user-agent');
445
        }
446
447
        // Captcha form
448
        $form = $this->getCurrentUrl();
449
        $captchas = $this->captcha;
450
451
        $ui = [
452
            'background_image' => '',
453
            'bg_color'         => '#ffffff',
454
            'header_bg_color'  => '#212531',
455
            'header_color'     => '#ffffff',
456
            'shadow_opacity'   => '0.2',
457
        ];
458
459
        foreach (array_keys($ui) as $key) {
460
            if (!empty($this->dialog[$key])) {
461
                $ui[$key] = $this->dialog[$key];
462
            }
463
        }
464
465
        if (!defined('SHIELDON_VIEW')) {
466
            define('SHIELDON_VIEW', true);
467
        }
468
469
        $css = include $this->getTemplate('css/default');
470
471
        ob_start();
472
        include $viewPath;
473
        $output = ob_get_contents();
474
        ob_end_clean();
475
476
        // Remove unused variable notices generated from PHP intelephense.
477
        unset(
478
            $css,
479
            $ui,
480
            $form,
481
            $captchas,
482
            $langCode,
483
            $showOnlineInformation,
484
            $showUserInformation
485
        );
486
487
        $stream = HttpFactory::createStream();
488
        $stream->write($output);
489
        $stream->rewind();
490
491
        return $response
492
            ->withHeader('X-Protected-By', 'shieldon.io')
493
            ->withBody($stream)
494
            ->withStatus($statusCode);
495
    }
496
497
    /**
498
     * Ban an IP.
499
     *
500
     * @param string $ip A valid IP address.
501
     *
502
     * @return void
503
     */
504
    public function ban(string $ip = ''): void
505
    {
506
        if ('' === $ip) {
507
            $ip = $this->ip;
508
        }
509
 
510
        $this->action(
511
            self::ACTION_DENY,
512
            self::REASON_MANUAL_BAN,
513
            $ip
514
        );
515
    }
516
517
    /**
518
     * Unban an IP.
519
     *
520
     * @param string $ip A valid IP address.
521
     *
522
     * @return void
523
     */
524
    public function unban(string $ip = ''): void
525
    {
526
        if ($ip === '') {
527
            $ip = $this->ip;
528
        }
529
530
        $this->action(
531
            self::ACTION_UNBAN,
532
            self::REASON_MANUAL_BAN,
533
            $ip
534
        );
535
        $this->log(self::ACTION_UNBAN);
536
537
        $this->result = self::RESPONSE_ALLOW;
538
    }
539
540
    /**
541
     * Set a property setting.
542
     *
543
     * @param string $key   The key of a property setting.
544
     * @param mixed  $value The value of a property setting.
545
     *
546
     * @return void
547
     */
548
    public function setProperty(string $key = '', $value = '')
549
    {
550
        if (isset($this->properties[$key])) {
551
            $this->properties[$key] = $value;
552
        }
553
    }
554
555
    /**
556
     * Set the property settings.
557
     * 
558
     * @param array $settings The settings.
559
     *
560
     * @return void
561
     */
562
    public function setProperties(array $settings): void
563
    {
564
        foreach (array_keys($this->properties) as $k) {
565
            if (isset($settings[$k])) {
566
                $this->properties[$k] = $settings[$k];
567
            }
568
        }
569
    }
570
571
    /**
572
     * Strict mode.
573
     * This option will take effects to all components.
574
     * 
575
     * @param bool $bool Set true to enble strict mode, false to disable it overwise.
576
     *
577
     * @return void
578
     */
579
    public function setStrict(bool $bool)
580
    {
581
        $this->strictMode = $bool;
582
    }
583
584
    /**
585
     * Set an action log logger.
586
     *
587
     * @param ActionLogger $logger Record action logs for users.
588
     *
589
     * @return void
590
     */
591
    public function setLogger(ActionLogger $logger): void
592
    {
593
        $this->logger = $logger;
594
    }
595
596
    /**
597
     * Add a path into the excluded list.
598
     *
599
     * @param string $uriPath The path component of a URI.
600
     * 
601
     * @return void
602
     */
603
    public function exclude(string $uriPath): void
604
    {
605
        $uriPath = '/' . ltrim($uriPath, '/');
606
607
        array_push($this->excludedUrls, $uriPath);
608
    }
609
610
    /**
611
     * Set the URLs you want them excluded them from protection.
612
     *
613
     * @param array $urls The list of URL want to be excluded.
614
     *
615
     * @return void
616
     */
617
    public function setExcludedList(array $urls = []): void
618
    {
619
        $this->excludedUrls = $urls;
620
    }
621
622
    /**
623
     * Set a closure function.
624
     *
625
     * @param string  $key     The name for the closure class.
626
     * @param Closure $closure An instance will be later called.
627
     *
628
     * @return void
629
     */
630
    public function setClosure(string $key, Closure $closure): void
631
    {
632
        $this->closures[$key] = $closure;
633
    }
634
635
    /**
636
     * Customize the dialog UI.
637
     * 
638
     * @param array $settings The dialog UI settings.
639
     *
640
     * @return void
641
     */
642
    public function setDialog(array $settings): void
643
    {
644
        $this->dialog = $settings;
645
    }
646
647
    /**
648
     * Set the frontend template directory.
649
     *
650
     * @param string $directory The directory in where the template files are placed.
651
     *
652
     * @return void
653
     */
654
    public function setTemplateDirectory(string $directory)
655
    {
656
        if (!is_dir($directory)) {
657
            throw new InvalidArgumentException(
658
                'The template directory does not exist.'
659
            );
660
        }
661
        $this->templateDirectory = $directory;
662
    }
663
664
    /**
665
     * Print a JavaScript snippet in your webpages.
666
     * 
667
     * This snippet generate cookie on client's browser,then we check the 
668
     * cookie to identify the client is a rebot or not.
669
     *
670
     * @return string
671
     */
672
    public function getJavascript(): string
673
    {
674
        $tmpCookieName = $this->properties['cookie_name'];
675
        $tmpCookieDomain = $this->properties['cookie_domain'];
676
677
        if (empty($tmpCookieDomain) && get_request()->getHeaderLine('host')) {
678
            $tmpCookieDomain = get_request()->getHeaderLine('host');
679
        }
680
681
        $tmpCookieValue = $this->properties['cookie_value'];
682
683
        $jsString = '
684
            <script>
685
                var d = new Date();
686
                d.setTime(d.getTime()+(60*60*24*30));
687
                document.cookie = "' . $tmpCookieName . '=' . $tmpCookieValue . ';domain=.' . $tmpCookieDomain . ';expires="+d.toUTCString();
688
            </script>
689
        ';
690
691
        return $jsString;
692
    }
693
694
    /**
695
     * Get current visior's path.
696
     *
697
     * @return string
698
     */
699
    public function getCurrentUrl(): string
700
    {
701
        return get_request()->getUri()->getPath();
702
    }
703
704
    /**
705
     * Displayed on Firewall Panel, telling you current what type of 
706
     * configuration is used.
707
     * 
708
     * @param string $type The type of configuration.
709
     *                     accepted value: demo | managed | config
710
     *
711
     * @return void
712
     */
713
    public function managedBy(string $type = ''): void
714
    {
715
        if (in_array($type, ['managed', 'config', 'demo'])) {
716
            $this->firewallType = $type;
717
        }
718
    }
719
720
    /*
721
    |-------------------------------------------------------------------
722
    | Non-public methids.
723
    |-------------------------------------------------------------------
724
    */
725
726
    /**
727
     * Run, run, run!
728
     *
729
     * Check the rule tables first, if an IP address has been listed.
730
     * Call function filter() if an IP address is not listed in rule tables.
731
     *
732
     * @return int The response code.
733
     */
734
    protected function process(): int
735
    {
736
        $this->driver->init($this->isCreateDatabase);
737
738
        $this->initComponents();
739
740
        $processMethods = [
741
            'isRuleExist',   // Stage 1 - Looking for rule table.
742
            'isTrustedBot',  // Stage 2 - Detect popular search engine.
743
            'isFakeRobot',   // Stage 3 - Reject fake search engine crawlers.
744
            'isIpComponent', // Stage 4 - IP manager.
745
            'isComponents'   // Stage 5 - Check other components.
746
        ];
747
748
        foreach ($processMethods as $method) {
749
            if ($this->{$method}()) {
750
                return $this->result;
751
            }
752
        }
753
754
        // Stage 6 - Check filters if set.
755
        if (array_search(true, $this->filterStatus)) {
756
            return $this->result = $this->sessionHandler($this->filter());
757
        }
758
759
        // Stage 7 - Go into session limit check.
760
        return $this->result = $this->sessionHandler(self::RESPONSE_ALLOW);
761
    }
762
763
    /**
764
     * Start an action for this IP address, allow or deny, and give a reason for it.
765
     *
766
     * @param int    $actionCode The action code. - 0: deny, 1: allow, 9: unban.
767
     * @param string $reasonCode The response code.
768
     * @param string $assignIp   The IP address.
769
     * 
770
     * @return void
771
     */
772
    protected function action(
773
        int    $actionCode,
774
        int    $reasonCode,
775
        string $assignIp = ''
776
    ): void {
777
778
        $ip = $this->ip;
779
        $rdns = $this->rdns;
780
        $now = time();
781
        $logData = [];
782
    
783
        if ('' !== $assignIp) {
784
            $ip = $assignIp;
785
            $rdns = gethostbyaddr($ip);
786
        }
787
788
        if ($actionCode === self::ACTION_UNBAN) {
789
            $this->driver->delete($ip, 'rule');
790
        } else {
791
            $logData['log_ip']     = $ip;
792
            $logData['ip_resolve'] = $rdns;
793
            $logData['time']       = $now;
794
            $logData['type']       = $actionCode;
795
            $logData['reason']     = $reasonCode;
796
            $logData['attempts']   = 0;
797
798
            $this->driver->save($ip, $logData, 'rule');
799
        }
800
801
        // Remove logs for this IP address because It already has it's own rule on system.
802
        // No need to count for it anymore.
803
        $this->driver->delete($ip, 'filter');
804
805
        // Log this action.
806
        $this->log($actionCode, $ip);
807
    }
808
809
    /**
810
     * Log actions.
811
     *
812
     * @param int    $actionCode The code number of the action.
813
     * @param string $ip         The IP address.
814
     *
815
     * @return void
816
     */
817
    protected function log(int $actionCode, $ip = ''): void
818
    {
819
        if (!$this->logger) {
820
            return;
821
        }
822
823
        $logData = [];
824
 
825
        $logData['ip'] = $ip ?: $this->getIp();
826
        $logData['session_id'] = get_session()->get('id');
827
        $logData['action_code'] = $actionCode;
828
        $logData['timesamp'] = time();
829
830
        $this->logger->add($logData);
831
    }
832
833
    /**
834
     * Get a template PHP file.
835
     *
836
     * @param string $type The template type.
837
     *
838
     * @return string
839
     */
840
    protected function getTemplate(string $type): string
841
    {
842
        $directory = self::KERNEL_DIR . '/../../templates/frontend';
843
844
        if (!empty($this->templateDirectory)) {
845
            $directory = $this->templateDirectory;
846
        }
847
848
        $path = $directory . '/' . $type . '.php';
849
850
        if (!file_exists($path)) {
851
            throw new RuntimeException(
852
                sprintf(
853
                    'The templeate file is missing. (%s)',
854
                    $path
855
                )
856
            );
857
        }
858
859
        return $path;
860
    }
861
862
    /**
863
     * Get a class name without namespace string.
864
     *
865
     * @param object $instance Class
866
     * 
867
     * @return string
868
     */
869
    protected function getClassName($instance): string
870
    {
871
        $class = get_class($instance);
872
        return substr($class, strrpos($class, '\\') + 1); 
873
    }
874
875
    /**
876
     * Save and return the result identifier.
877
     * This method is for passing value from traits.
878
     *
879
     * @param int $resultCode The result identifier.
880
     *
881
     * @return int
882
     */
883
    protected function setResultCode(int $resultCode): int
884
    {
885
        return $this->result = $resultCode;
886
    }
887
}
888