Passed
Push — 2.x ( d6434d...dbf03f )
by Terry
01:59
created

Kernel::setChannel()   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 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
/*
3
 * @name        Shieldon Firewall
4
 * @author      Terry Lin
5
 * @link        https://github.com/terrylinooo/shieldon
6
 * @package     Shieldon
7
 * @since       1.0.0
8
 * @version     2.0.0
9
 * @license     MIT
10
 *
11
 * Permission is hereby granted, free of charge, to any person obtaining a copy
12
 * of this software and associated documentation files (the "Software"), to deal
13
 * in the Software without restriction, including without limitation the rights
14
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
 * copies of the Software, and to permit persons to whom the Software is
16
 * furnished to do so, subject to the following conditions:
17
 *
18
 * The above copyright notice and this permission notice shall be included in
19
 * all copies or substantial portions of the Software.
20
 *
21
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
 * THE SOFTWARE.
28
 */
29
30
declare(strict_types=1);
31
32
namespace Shieldon\Firewall;
33
34
use Psr\Http\Message\ResponseInterface;
35
use Psr\Http\Message\ServerRequestInterface;
36
use Shieldon\Firewall\Captcha\Foundation;
37
use Shieldon\Firewall\Helpers;
38
use Shieldon\Firewall\HttpFactory;
39
use Shieldon\Firewall\IpTrait;
40
use Shieldon\Firewall\Kernel\CaptchaTrait;
41
use Shieldon\Firewall\Kernel\ComponentTrait;
42
use Shieldon\Firewall\Kernel\FilterTrait;
43
use Shieldon\Firewall\Kernel\MessengerTrait;
44
use Shieldon\Firewall\Kernel\RuleTrait;
45
use Shieldon\Firewall\Kernel\SessionTrait;
46
use Shieldon\Firewall\Log\ActionLogger;
47
use Shieldon\Firewall\Utils\Container;
48
use function Shieldon\Firewall\get_default_properties;
49
use function Shieldon\Firewall\get_request;
50
use function Shieldon\Firewall\get_response;
51
use function Shieldon\Firewall\get_session;
52
53
use Closure;
54
use InvalidArgumentException;
55
use RuntimeException;
56
use function file_exists;
57
use function get_class;
58
use function gethostbyaddr;
59
use function is_dir;
60
use function ob_end_clean;
61
use function ob_get_contents;
62
use function ob_start;
63
use function strpos;
64
use function strrpos;
65
use function substr;
66
use function time;
67
68
/**
69
 * The primary Shiendon class.
70
 */
71
class Kernel
72
{
73
    use CaptchaTrait;
74
    use ComponentTrait;
75
    use DriverTrait;
0 ignored issues
show
Bug introduced by
The type Shieldon\Firewall\DriverTrait 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...
76
    use FilterTrait;
77
    use IpTrait;
78
    use MessengerTrait;
79
    use RuleTrait;
80
    use SessionTrait;
81
82
    // Reason codes (allow)
83
    const REASON_IS_SEARCH_ENGINE = 100;
84
    const REASON_IS_GOOGLE = 101;
85
    const REASON_IS_BING = 102;
86
    const REASON_IS_YAHOO = 103;
87
    const REASON_IS_SOCIAL_NETWORK = 110;
88
    const REASON_IS_FACEBOOK = 111;
89
    const REASON_IS_TWITTER = 112;
90
91
    // Reason codes (deny)
92
    const REASON_TOO_MANY_SESSIONS = 1;
93
    const REASON_TOO_MANY_ACCESSES = 2; // (not used)
94
    const REASON_EMPTY_JS_COOKIE = 3;
95
    const REASON_EMPTY_REFERER = 4;
96
    
97
    const REASON_REACHED_LIMIT_DAY = 11;
98
    const REASON_REACHED_LIMIT_HOUR = 12;
99
    const REASON_REACHED_LIMIT_MINUTE = 13;
100
    const REASON_REACHED_LIMIT_SECOND = 14;
101
102
    const REASON_INVALID_IP = 40;
103
    const REASON_DENY_IP = 41;
104
    const REASON_ALLOW_IP = 42;
105
106
    const REASON_COMPONENT_IP = 81;
107
    const REASON_COMPONENT_RDNS = 82;
108
    const REASON_COMPONENT_HEADER = 83;
109
    const REASON_COMPONENT_USERAGENT = 84;
110
    const REASON_COMPONENT_TRUSTED_ROBOT = 85;
111
112
    const REASON_MANUAL_BAN = 99;
113
114
    // Action codes
115
    const ACTION_DENY = 0;
116
    const ACTION_ALLOW = 1;
117
    const ACTION_TEMPORARILY_DENY = 2;
118
    const ACTION_UNBAN = 9;
119
120
    // Result codes
121
    const RESPONSE_DENY = 0;
122
    const RESPONSE_ALLOW = 1;
123
    const RESPONSE_TEMPORARILY_DENY = 2;
124
    const RESPONSE_LIMIT_SESSION = 3;
125
126
    const LOG_LIMIT = 3;
127
    const LOG_PAGEVIEW = 11;
128
    const LOG_BLACKLIST = 98;
129
    const LOG_CAPTCHA = 99;
130
131
    const KERNEL_DIR = __DIR__;
132
133
    /**
134
     * The result passed from filters, compoents, etc.
135
     * 
136
     * DENY    : 0
137
     * ALLOW   : 1
138
     * CAPTCHA : 2
139
     *
140
     * @var int
141
     */
142
    protected $result = 1;
143
144
    /**
145
     * default settings
146
     *
147
     * @var array
148
     */
149
    protected $properties = [];
150
151
    /**
152
     * Logger instance.
153
     *
154
     * @var ActionLogger
155
     */
156
    public $logger;
157
158
    /**
159
     * The closure functions that will be executed in this->run()
160
     *
161
     * @var array
162
     */
163
    protected $closures = [];
164
165
    /**
166
     * URLs that are excluded from Shieldon's protection.
167
     *
168
     * @var array
169
     */
170
    protected $excludedUrls = [];
171
172
    /**
173
     * Custom dialog UI settings.
174
     *
175
     * @var array
176
     */
177
    protected $dialogUI = [];
178
179
    /**
180
     * Strict mode.
181
     * 
182
     * Set by `strictMode()` only. The default value of this propertry is undefined.
183
     *
184
     * @var bool|null
185
     */
186
    protected $strictMode;
187
188
    /**
189
     * The directory in where the frontend template files are placed.
190
     *
191
     * @var string
192
     */
193
    protected $templateDirectory = '';
194
195
    /**
196
     * Which type of configuration source that Shieldon firewall managed?
197
     * value: managed | config | self | demo
198
     *
199
     * @var string
200
     */
201
    protected $firewallType = 'self'; 
202
203
    /**
204
     * Shieldon constructor.
205
     * 
206
     * @param ServerRequestInterface|null $request  A PSR-7 server request.
207
     * 
208
     * @return void
209
     */
210
    public function __construct(?ServerRequestInterface $request  = null, ?ResponseInterface $response = null)
211
    {
212
        // Load helper functions. This is the must.
213
        new Helpers();
214
215
        $request = $request ?? HttpFactory::createRequest();
216
        $response = $response ?? HttpFactory::createResponse();
217
        $session = HttpFactory::createSession();
218
219
        $this->properties = get_default_properties();
220
        $this->setCaptcha(new Foundation());
221
222
        Container::set('request', $request);
223
        Container::set('response', $response);
224
        Container::set('session', $session);
225
        Container::set('shieldon', $this);
226
    }
227
228
    /**
229
     * Run, run, run!
230
     *
231
     * Check the rule tables first, if an IP address has been listed.
232
     * Call function filter() if an IP address is not listed in rule tables.
233
     *
234
     * @return 
235
     */
236
    public function run(): int
237
    {
238
        if (!isset($this->driver)) {
239
            throw new RuntimeException(
240
                'Must register at least one data driver.'
241
            );
242
        }
243
        
244
        // Ignore the excluded urls.
245
        if (!empty($this->excludedUrls)) {
246
            foreach ($this->excludedUrls as $url) {
247
                if (0 === strpos(get_request()->getUri()->getPath(), $url)) {
248
                    return $this->result = self::RESPONSE_ALLOW;
249
                }
250
            }
251
        }
252
253
        // Execute closure functions.
254
        foreach ($this->closures as $closure) {
255
            $closure();
256
        }
257
258
        $result = $this->process();
259
260
        if ($result !== self::RESPONSE_ALLOW) {
261
262
            // Current session did not pass the CAPTCHA, it is still stuck in CAPTCHA page.
263
            $actionCode = self::LOG_CAPTCHA;
264
265
            // If current session's respone code is RESPONSE_DENY, record it as `blacklist_count` in our logs.
266
            // It is stuck in warning page, not CAPTCHA.
267
            if ($result === self::RESPONSE_DENY) {
268
                $actionCode = self::LOG_BLACKLIST;
269
            }
270
271
            if ($result === self::RESPONSE_LIMIT_SESSION) {
272
                $actionCode = self::LOG_LIMIT;
273
            }
274
275
            $this->log($actionCode);
276
277
        } else {
278
279
            $this->log(self::LOG_PAGEVIEW);
280
        }
281
282
        // @ MessengerTrait
283
        $this->triggerMessengers();
284
285
        return $result;
286
    }
287
288
    /**
289
     * Respond the result.
290
     *
291
     * @return ResponseInterface
292
     */
293
    public function respond(): ResponseInterface
294
    {
295
        $response = get_response();
296
        $type = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $type is dead and can be removed.
Loading history...
297
298
        $httpStatusCodes = [
299
            self::RESPONSE_TEMPORARILY_DENY => [
300
                'type' => 'captcha',
301
                'code' => 403, // Forbidden.
302
            ],
303
304
            self::RESPONSE_LIMIT_SESSION => [
305
                'type' => 'session_limitation',
306
                'code' => 429, // Too Many Requests.
307
            ],
308
309
            self::RESPONSE_DENY => [
310
                'type' => 'rejection',
311
                'code' => 400, // Bad request.
312
            ],
313
        ];
314
315
        // Nothing happened. Return.
316
        if (empty($httpStatusCodes[$this->result])) {
317
            return $response;
318
        }
319
320
        $type = $httpStatusCodes[$this->result]['type'];
321
        $statusCode = $httpStatusCodes[$this->result]['code'];
322
323
        $viewPath = $this->getTemplate($type);
324
325
        // The language of output UI. It is used on views.
326
        $langCode = get_session()->get('shieldon_ui_lang') ?? 'en';
327
328
        $showOnlineInformation = false;
329
        $showUserInformation = false;
330
        
331
        // Show online session count. It is used on views.
332
        if (!empty($this->properties['display_online_info'])) {
333
            $showOnlineInformation = true;
334
            $onlineinfo['queue'] = $this->sessionStatus['queue'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$onlineinfo was never initialized. Although not strictly required by PHP, it is generally a good practice to add $onlineinfo = array(); before regardless.
Loading history...
335
            $onlineinfo['count'] = $this->sessionStatus['count'];
336
            $onlineinfo['period'] = $this->sessionLimit['period'];
337
        } 
338
339
        // Show user information such as IP, user-agent, device name.
340
        if (!empty($this->properties['display_user_info'])) {
341
            $showUserInformation = true;
342
            $dialoguserinfo['ip'] = $this->ip;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$dialoguserinfo was never initialized. Although not strictly required by PHP, it is generally a good practice to add $dialoguserinfo = array(); before regardless.
Loading history...
343
            $dialoguserinfo['rdns'] = $this->rdns;
344
            $dialoguserinfo['user_agent'] = get_request()->getHeaderLine('user-agent');
345
        }
346
347
        // Captcha form
348
        $form = $this->getCurrentUrl();
349
        $captchas = $this->captcha;
350
351
        $ui = [
352
            'background_image' => '',
353
            'bg_color'         => '#ffffff',
354
            'header_bg_color'  => '#212531',
355
            'header_color'     => '#ffffff',
356
            'shadow_opacity'   => '0.2',
357
        ];
358
359
        foreach (array_keys($ui) as $key) {
360
            if (!empty($this->dialogUI[$key])) {
361
                $ui[$key] = $this->dialogUI[$key];
362
            }
363
        }
364
365
        if (!defined('SHIELDON_VIEW')) {
366
            define('SHIELDON_VIEW', true);
367
        }
368
369
        $css = require $this->getTemplate('css/default');
370
371
        ob_start();
372
        require $viewPath;
373
        $output = ob_get_contents();
374
        ob_end_clean();
375
376
        // Remove unused variable notices generated from PHP intelephense.
377
        unset(
378
            $css,
379
            $ui,
380
            $form,
381
            $captchas,
382
            $csrf,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $csrf seems to be never defined.
Loading history...
383
            $langCode,
384
            $showOnlineInformation,
385
            $showUserInformation
386
        );
387
388
        $stream = $response->getBody();
389
        $stream->write($output);
390
        $stream->rewind();
391
392
        return $response->
393
            withHeader('X-Protected-By', 'shieldon.io')->
394
            withBody($stream)->
395
            withStatus($statusCode);
396
    }
397
398
    /**
399
     * Ban an IP.
400
     *
401
     * @param string $ip A valid IP address.
402
     *
403
     * @return void
404
     */
405
    public function ban(string $ip = ''): void
406
    {
407
        if ('' === $ip) {
408
            $ip = $this->ip;
409
        }
410
 
411
        $this->action(
412
            self::ACTION_DENY,
413
            self::REASON_MANUAL_BAN,
414
            $ip
415
        );
416
    }
417
418
    /**
419
     * Unban an IP.
420
     *
421
     * @param string $ip A valid IP address.
422
     *
423
     * @return void
424
     */
425
    public function unban(string $ip = ''): void
426
    {
427
        if ('' === $ip) {
428
            $ip = $this->ip;
429
        }
430
431
        $this->action(
432
            self::ACTION_UNBAN,
433
            self::REASON_MANUAL_BAN,
434
            $ip
435
        );
436
        $this->log(self::ACTION_UNBAN);
437
438
        $this->result = self::RESPONSE_ALLOW;
439
    }
440
441
    /**
442
     * Set a property setting.
443
     *
444
     * @param string $key   The key of a property setting.
445
     * @param mixed  $value The value of a property setting.
446
     *
447
     * @return void
448
     */
449
    public function setProperty(string $key = '', $value = '')
450
    {
451
        if (isset($this->properties[$key])) {
452
            $this->properties[$key] = $value;
453
        }
454
    }
455
456
    /**
457
     * Set the property settings.
458
     * 
459
     * @param array $settings The settings.
460
     *
461
     * @return void
462
     */
463
    public function setProperties(array $settings): void
464
    {
465
        foreach (array_keys($this->properties) as $k) {
466
            if (isset($settings[$k])) {
467
                $this->properties[$k] = $settings[$k];
468
            }
469
        }
470
    }
471
472
    /**
473
     * Strict mode.
474
     * This option will take effects to all components.
475
     * 
476
     * @param bool $bool Set true to enble strict mode, false to disable it overwise.
477
     *
478
     * @return void
479
     */
480
    public function setStrict(bool $bool)
481
    {
482
        $this->strictMode = $bool;
483
    }
484
485
    /**
486
     * Set a action log logger.
487
     *
488
     * @param ActionLogger $logger
489
     *
490
     * @return void
491
     */
492
    public function setLogger(ActionLogger $logger): void
493
    {
494
        $this->logger = $logger;
495
    }
496
497
    /**
498
     * Set the URLs you want them to be excluded them from protection.
499
     *
500
     * @param array $urls The list of URL want to be excluded.
501
     *
502
     * @return void
503
     */
504
    public function setExcludedUrls(array $urls = []): void
505
    {
506
        $this->excludedUrls = $urls;
507
    }
508
509
    /**
510
     * Set a closure function.
511
     *
512
     * @param string  $key     The name for the closure class.
513
     * @param Closure $closure An instance will be later called.
514
     *
515
     * @return void
516
     */
517
    public function setClosure(string $key, Closure $closure): void
518
    {
519
        $this->closures[$key] = $closure;
520
    }
521
522
    /**
523
     * Customize the dialog UI.
524
     *
525
     * @return void
526
     */
527
    public function setDialogUI(array $settings): void
528
    {
529
        $this->dialogUI = $settings;
530
    }
531
532
    /**
533
     * Set the frontend template directory.
534
     *
535
     * @param string $directory
536
     *
537
     * @return void
538
     */
539
    public function setTemplateDirectory(string $directory)
540
    {
541
        if (!is_dir($directory)) {
542
            throw new InvalidArgumentException('The template directory does not exist.');
543
        }
544
        $this->templateDirectory = $directory;
545
    }
546
547
    /**
548
     * Get a template PHP file.
549
     *
550
     * @param string $type The template type.
551
     *
552
     * @return string
553
     */
554
    protected function getTemplate(string $type): string
555
    {
556
        $directory = self::KERNEL_DIR . '/../../templates/frontend';
557
558
        if (!empty($this->templateDirectory)) {
559
            $directory = $this->templateDirectory;
560
        }
561
562
        $path = $directory . '/' . $type . '.php';
563
564
        if (!file_exists($path)) {
565
            throw new RuntimeException(
566
                sprintf(
567
                    'The templeate file is missing. (%s)',
568
                    $path
569
                )
570
            );
571
        }
572
573
        return $path;
574
    }
575
576
    /**
577
     * Get a class name without namespace string.
578
     *
579
     * @param object $instance Class
580
     * 
581
     * @return void
582
     */
583
    protected function getClassName($instance): string
584
    {
585
        $class = get_class($instance);
586
        return substr($class, strrpos($class, '\\') + 1); 
0 ignored issues
show
Bug Best Practice introduced by
The expression return substr($class, strrpos($class, '\') + 1) returns the type string which is incompatible with the documented return type void.
Loading history...
587
    }
588
589
    /**
590
     * Print a JavasSript snippet in your webpages.
591
     * 
592
     * This snippet generate cookie on client's browser,then we check the 
593
     * cookie to identify the client is a rebot or not.
594
     *
595
     * @return string
596
     */
597
    public function getJavascript(): string
598
    {
599
        $tmpCookieName = $this->properties['cookie_name'];
600
        $tmpCookieDomain = $this->properties['cookie_domain'];
601
602
        if (empty($tmpCookieDomain) && get_request()->getHeaderLine('host')) {
603
            $tmpCookieDomain = get_request()->getHeaderLine('host');
604
        }
605
606
        $tmpCookieValue = $this->properties['cookie_value'];
607
608
        $jsString = '
609
            <script>
610
                var d = new Date();
611
                d.setTime(d.getTime()+(60*60*24*30));
612
                document.cookie = "' . $tmpCookieName . '=' . $tmpCookieValue . ';domain=.' . $tmpCookieDomain . ';expires="+d.toUTCString();
613
            </script>
614
        ';
615
616
        return $jsString;
617
    }
618
619
    /**
620
     * Get current visior's path.
621
     *
622
     * @return string
623
     */
624
    public function getCurrentUrl(): string
625
    {
626
        return get_request()->getUri()->getPath();
627
    }
628
629
    /**
630
     * Displayed on Firewall Panel, tell you current what type of current
631
     * configuration is used for.
632
     * 
633
     * @param string $type The type of configuration.
634
     *                     demo | managed | config
635
     *
636
     * @return void
637
     */
638
    public function managedBy(string $type = ''): void
639
    {
640
        if (in_array($type, ['managed', 'config', 'demo'])) {
641
            $this->firewallType = $type;
642
        }
643
    }
644
645
    /*
646
    |-------------------------------------------------------------------
647
    | Non-public methids.
648
    |-------------------------------------------------------------------
649
    */
650
651
    /**
652
     * Run, run, run!
653
     *
654
     * Check the rule tables first, if an IP address has been listed.
655
     * Call function filter() if an IP address is not listed in rule tables.
656
     *
657
     * @return int The response code.
658
     */
659
    protected function process(): int
660
    {
661
        $this->driver->init($this->autoCreateDatabase);
662
663
        $this->initComponents();
664
665
        // Stage 1. - Looking for rule table.
666
        if ($this->IsRuleExist()) {
667
            return $this->result;
668
        }
669
670
        // Statge 2a - Detect popular search engine.
671
        if ($this->isTrustedBot()) {
672
            return $this->result;
673
        }
674
675
        // Statge 2b - Reject fake search engine crawlers.
676
        if ($this->isFakeRobot()) {
677
            return $this->result;
678
        }
679
        
680
        // Stage 3 - IP manager.
681
        if ($this->isIpComponent()) {
682
            return $this->result;
683
        }
684
685
        // Stage 4 - Check other components.
686
        foreach ($this->component as $component) {
687
688
            // check if is a a bad robot already defined in settings.
689
            if ($component->isDenied()) {
690
691
                $this->action(
692
                    self::ACTION_DENY,
693
                    $component->getDenyStatusCode()
694
                );
695
696
                return $this->result = self::RESPONSE_DENY;
697
            }
698
        }
699
700
        // Stage 5 - Check filters if set.
701
        if (array_search(true, $this->filterStatus)) {
702
            return $this->result = $this->sessionHandler($this->filter());
703
        }
704
705
        // Stage 6 - Go into session limit check.
706
        return $this->result = $this->sessionHandler(self::RESPONSE_ALLOW);
707
    }
708
709
    /**
710
     * Start an action for this IP address, allow or deny, and give a reason for it.
711
     *
712
     * @param int    $actionCode - 0: deny, 1: allow, 9: unban.
713
     * @param string $reasonCode
714
     * @param string $assignIp
715
     * 
716
     * @return void
717
     */
718
    protected function action(int $actionCode, int $reasonCode, string $assignIp = ''): void
719
    {
720
        $ip = $this->ip;
721
        $rdns = $this->rdns;
722
        $now = time();
723
        $logData = [];
724
    
725
        if ('' !== $assignIp) {
726
            $ip = $assignIp;
727
            $rdns = gethostbyaddr($ip);
728
        }
729
730
        if ($actionCode === self::ACTION_UNBAN) {
731
            $this->driver->delete($ip, 'rule');
732
        } else {
733
            $logData['log_ip']     = $ip;
734
            $logData['ip_resolve'] = $rdns;
735
            $logData['time']       = $now;
736
            $logData['type']       = $actionCode;
737
            $logData['reason']     = $reasonCode;
738
            $logData['attempts']   = 0;
739
740
            $this->driver->save($ip, $logData, 'rule');
741
        }
742
743
        // Remove logs for this IP address because It already has it's own rule on system.
744
        // No need to count for it anymore.
745
        $this->driver->delete($ip, 'filter');
746
747
        // Log this action.
748
        $this->log($actionCode, $ip);
749
    }
750
751
    /**
752
     * Log actions.
753
     *
754
     * @param int $actionCode The code number of the action.
755
     *
756
     * @return void
757
     */
758
    protected function log(int $actionCode, $ip = ''): void
759
    {
760
        if (!$this->logger) {
761
            return;
762
        }
763
764
        $logData = [];
765
        $logData['ip'] = $ip ?? $this->getIp();
766
        $logData['session_id'] = get_session()->get('id');
767
        $logData['action_code'] = $actionCode;
768
        $logData['timesamp'] = time();
769
770
        $this->logger->add($logData);
771
    }
772
}
773