Passed
Push — 2.x ( a4ac50...e966a7 )
by Terry
01:56
created

Firewall::setIpSource()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 7
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 22
rs 10
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;
14
15
use Psr\Http\Message\ServerRequestInterface;
16
use Psr\Http\Message\ResponseInterface;
17
use Psr\Http\Server\MiddlewareInterface;
18
use Shieldon\Firewall\Kernel;
19
use Shieldon\Firewall\Captcha as Captcha;
20
use Shieldon\Firewall\Driver as Driver;
21
use Shieldon\Firewall\Middleware as Middleware;
22
23
use Shieldon\Messenger as Messenger;
24
use Shieldon\Firewall\Messenger\MessengerFactory;
25
use Shieldon\Firewall\Utils\Container;
26
use Shieldon\Firewall\Log\ActionLogger;
27
use Shieldon\Firewall\Traits\FirewallTrait;
28
use Shieldon\Firewall\Traits\Firewall\XssProtectionTrait;
29
use Shieldon\Psr15\RequestHandler;
30
use function Shieldon\Firewall\get_request;
31
use function Shieldon\Firewall\get_response;
32
33
use PDO;
34
use PDOException;
35
use Redis;
36
use RedisException;
37
use RuntimeException;
38
39
use function array_column;
40
use function defined;
41
use function file_exists;
42
use function file_get_contents;
43
use function file_put_contents;
44
use function is_dir;
45
use function json_decode;
46
use function json_encode;
47
use function mkdir;
48
use function rtrim;
49
use function strpos;
50
use function umask;
51
use function time;
52
use function strtotime;
53
use function date;
54
55
/**
56
 * Managed Firewall.
57
 */
58
class Firewall
59
{
60
    use FirewallTrait;
61
    use XssProtectionTrait;
62
63
    /**
64
     * Collection of PSR-7 or PSR-15 middlewares.
65
     *
66
     * @var array
67
     */
68
    protected $middlewares = [];
69
70
    /**
71
     * Constructor.
72
     */
73
    public function __construct(?ServerRequestInterface $request = null, ?ResponseInterface $response = null)
74
    {
75
        Container::set('firewall', $this);
76
77
        $this->kernel = new Kernel($request, $response);
78
    }
79
80
    /**
81
     * Set up the path of the configuration file.
82
     *
83
     * @param string $source The path.
84
     * @param string $type   The type.
85
     * 
86
     * @return void
87
     */
88
    public function configure(string $source, string $type = 'json')
89
    {
90
        if ($type === 'json') {
91
            $this->directory = rtrim($source, '\\/');
92
            $configFilePath = $this->directory . '/' . $this->filename;
93
94
            if (file_exists($configFilePath)) {
95
                $jsonString = file_get_contents($configFilePath);
96
97
            } else {
98
                $jsonString = file_get_contents(__DIR__ . '/../../config.json');
99
100
                if (defined('PHP_UNIT_TEST')) {
101
                    $jsonString = file_get_contents(__DIR__ . '/../../tests/config.json');
102
                }
103
            }
104
105
            $this->configuration = json_decode($jsonString, true);
106
            $this->kernel->managedBy('managed');
107
108
        } elseif ($type === 'php') {
109
            $this->configuration = require $source;
110
            $this->kernel->managedBy('config');
111
        }
112
113
        $this->setup();
114
    }
115
116
    /**
117
     * Add middlewares and use them before going into Shieldon kernal.
118
     *
119
     * @param MiddlewareInterface $middleware A PSR-15 middlewares.
120
     *
121
     * @return void
122
     */
123
    public function add(MiddlewareInterface $middleware)
124
    {
125
        $this->middlewares[] = $middleware;
126
    }
127
128
    /**
129
     * Setup everything we need.
130
     *
131
     * @return void
132
     */
133
    public function setup(): void
134
    {
135
        $this->status = $this->getOption('daemon');
136
137
        $this->setDriver();
138
139
        $this->setChannel();
140
141
        $this->setIpSource();
142
143
        $this->setLogger();
144
145
        $this->setFilters();
146
147
        $this->setComponents();
148
149
        $this->setCaptchas();
150
151
        $this->setSessionLimit();
152
153
        $this->setCronJob();
154
155
        $this->setExcludedUrls();
156
157
        $this->setXssProtection();
158
159
        $this->setAuthentication();
160
161
        $this->setDialogUI();
162
163
        $this->setMessengers();
164
165
        $this->setMessageEvents();
166
167
        $this->setDenyAttempts();
168
169
        $this->setIptablesWatchingFolder();
170
    }
171
172
    /**
173
     * Just, run!
174
     *
175
     * @return ResponseInterface
176
     */
177
    public function run(): ResponseInterface
178
    {
179
        // If settings are ready, let's start monitoring requests.
180
        if ($this->status) {
181
182
            $response = get_request();
183
184
            // PSR-15 request handler.
185
            $requestHandler = new RequestHandler();
186
187
            foreach ($this->middlewares as $middleware) {
188
                $requestHandler->add($middleware);
189
            }
190
191
            $response = $requestHandler->handle($response);
192
193
            // Something is detected by Middlewares, return.
194
            if ($response->getStatusCode() !== 200) {
195
                return $response;
196
            }
197
198
            $result = $this->kernel->run();
199
200
            if ($result !== $this->kernel::RESPONSE_ALLOW) {
201
202
                if ($this->kernel->captchaResponse()) {
203
                    $this->kernel->unban();
204
205
                    $response = $response->withHeader('Location', $this->kernel->getCurrentUrl());
206
                    $response = $response->withStatus(303);
207
208
                    return $response;
209
                }
210
            }
211
        }
212
213
        return $this->kernel->respond();
214
    }
215
216
    /**
217
     * Set the channel ID.
218
     *
219
     * @return void
220
     */
221
    protected function setChannel(): void
222
    {
223
        $channelId = $this->getOption('channel_id');
224
225
        if ($channelId) {
226
            $this->kernel->setChannel($channelId);
227
        }
228
    }
229
230
    /**
231
     * Set a data driver for Shieldon use.
232
     *
233
     * @return void
234
     */
235
    protected function setDriver(): void
236
    {
237
        $driverType = $this->getOption('driver_type');
238
239
        switch ($driverType) {
240
241
            case 'redis':
242
            
243
                $redisSetting = $this->getOption('redis', 'drivers');
244
245
                try {
246
247
                    $host = '127.0.0.1';
248
                    $port = 6379;
249
250
                    if (!empty($redisSetting['host'])) {
251
                        $host = $redisSetting['host'];
252
                    }
253
254
                    if (!empty($redisSetting['port'])) {
255
                        $port = $redisSetting['port'];
256
                    }
257
258
                    // Create a Redis instance.
259
                    $redis = new Redis();
260
                    $redis->connect($host, $port);
261
262
                    if (!empty($redisSetting['auth'])) {
263
264
                        // @codeCoverageIgnoreStart
265
                        $redis->auth($redisSetting['auth']);
266
                        // @codeCoverageIgnoreEnd
267
                    }
268
269
                    // Use Redis data driver.
270
                    $this->kernel->add(new Driver\RedisDriver($redis));
271
272
                // @codeCoverageIgnoreStart
273
                } catch(RedisException $e) {
274
                    $this->status = false;
275
276
                    echo $e->getMessage();
277
                }
278
                // @codeCoverageIgnoreEnd
279
280
                break;
281
282
            case 'file':
283
            
284
                $fileSetting = $this->getOption('file', 'drivers');
285
286
                if (empty($fileSetting['directory_path'])) {
287
                    $fileSetting['directory_path'] = $this->directory;
288
                }
289
290
                // Use File data driver.
291
                $this->kernel->add(new Driver\FileDriver($fileSetting['directory_path']));
292
293
                break;
294
295
            case 'sqlite':
296
            
297
                $sqliteSetting = $this->getOption('sqlite', 'drivers');
298
299
                if (empty($sqliteSetting['directory_path'])) {
300
                    $sqliteSetting['directory_path'] = '';
301
                    $this->status = false;
302
                }
303
304
                try {
305
                    
306
                    // Specific the sqlite file location.
307
                    $sqliteLocation = $sqliteSetting['directory_path'] . '/shieldon.sqlite3';
308
309
                    // Create a PDO instance.
310
                    $pdoInstance = new PDO('sqlite:' . $sqliteLocation);
311
312
                    // Use Sqlite data driver.
313
                    $this->kernel->add(new Driver\SqliteDriver($pdoInstance));
314
    
315
                // @codeCoverageIgnoreStart
316
                } catch(PDOException $e) {
317
                    $this->status = false;
318
319
                    echo $e->getMessage();
320
                }
321
                // @codeCoverageIgnoreEnd
322
323
                break;
324
325
            case 'mysql':
326
            default:
327
328
                $mysqlSetting = $this->getOption('mysql', 'drivers');
329
330
                try {
331
332
                    // Create a PDO instance.
333
                    $pdoInstance = new PDO(
334
                        'mysql:host=' 
335
                            . $mysqlSetting['host']   . ';dbname=' 
336
                            . $mysqlSetting['dbname'] . ';charset=' 
337
                            . $mysqlSetting['charset']
338
                        , (string) $mysqlSetting['user']
339
                        , (string) $mysqlSetting['pass']
340
                    );
341
342
                    // Use MySQL data driver.
343
                    $this->kernel->add(new Driver\MysqlDriver($pdoInstance));
344
345
                // @codeCoverageIgnoreStart
346
                } catch(PDOException $e) {
347
                    echo $e->getMessage();
348
                }
349
                // @codeCoverageIgnoreEnd
350
            // end switch.
351
        }
352
    }
353
354
    /**
355
     * Set up the action logger.
356
     *
357
     * @return void
358
     */
359
    protected function setLogger(): void
360
    {
361
        $loggerSetting = $this->getOption('action', 'loggers');
362
363
        if ($loggerSetting['enable']) {
364
            if (!empty($loggerSetting['config']['directory_path'])) {
365
                $this->kernel->add(new ActionLogger($loggerSetting['config']['directory_path']));
366
            }
367
        }
368
    }
369
370
    /**
371
     * If you use CDN, please choose the real IP source.
372
     *
373
     * @return void
374
     */
375
    protected function setIpSource(): void
376
    {
377
        $ipSourceType = $this->getOption('ip_variable_source');
378
        $serverParams = get_request()->getServerParams();
379
380
        /**
381
         * REMOTE_ADDR: general
382
         * HTTP_CF_CONNECTING_IP: Cloudflare
383
         * HTTP_X_FORWARDED_FOR: Google Cloud CDN, Google Load-balancer, AWS.
384
         * HTTP_X_FORWARDED_HOST: KeyCDN, or other CDN providers not listed here.
385
         * 
386
         */
387
        $key = array_search(true, $ipSourceType);
0 ignored issues
show
Bug introduced by
It seems like $ipSourceType can also be of type false; however, parameter $haystack of array_search() 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
        $key = array_search(true, /** @scrutinizer ignore-type */ $ipSourceType);
Loading history...
388
        $ip = $serverParams[$key];
389
390
        if (empty($ip)) {
391
            // @codeCoverageIgnoreStart
392
            throw new RuntimeException('IP source is not set correctly.');
393
            // @codeCoverageIgnoreEnd
394
        }
395
396
        $this->kernel->setIp($ip);
397
    }
398
399
    /**
400
     * Set the filiters.
401
     *
402
     * @return void
403
     */
404
    protected function setFilters(): void
405
    {
406
        $sessionSetting   = $this->getOption('session', 'filters');
407
        $cookieSetting    = $this->getOption('cookie', 'filters');
408
        $refererSetting   = $this->getOption('referer', 'filters');
409
        $frequencySetting = $this->getOption('frequency', 'filters');
410
411
        $filterConfig = [
412
            'session'   => $sessionSetting['enable'],
413
            'cookie'    => $cookieSetting['enable'],
414
            'referer'   => $refererSetting['enable'],
415
            'frequency' => $frequencySetting['enable'],
416
        ];
417
418
        $this->kernel->setFilters($filterConfig);
419
420
        $this->kernel->setProperty('limit_unusual_behavior', [
421
            'session' => $sessionSetting['config']['quota'] ?? 5,
422
            'cookie'  => $cookieSetting['config']['quota'] ?? 5,
423
            'referer' => $refererSetting['config']['quota'] ?? 5,
424
        ]);
425
426
        // if ($frequencySetting['enable']) {
427
        $frequencyQuota = [
428
            's' => $frequencySetting['config']['quota_s'] ?? 2,
429
            'm' => $frequencySetting['config']['quota_m'] ?? 10,
430
            'h' => $frequencySetting['config']['quota_h'] ?? 30,
431
            'd' => $frequencySetting['config']['quota_d'] ?? 60,
432
        ];
433
434
        $this->kernel->setProperty('time_unit_quota', $frequencyQuota);
435
436
        // if ($cookieSetting['enable']) {
437
        $cookieName = $cookieSetting['config']['cookie_name'] ?? 'ssjd';
438
        $cookieDomain = $cookieSetting['config']['cookie_domain'] ?? '';
439
        $cookieValue = $cookieSetting['config']['cookie_value'] ?? '1';
440
441
        $this->kernel->setProperty('cookie_name', $cookieName);
442
        $this->kernel->setProperty('cookie_domain', $cookieDomain);
443
        $this->kernel->setProperty('cookie_value', $cookieValue);
444
445
        // if ($refererSetting['enable']) {
446
        $this->kernel->setProperty('interval_check_referer', $refererSetting['config']['time_buffer']);
447
448
        // if ($sessionSetting['enable']) {
449
        $this->kernel->setProperty('interval_check_session', $sessionSetting['config']['time_buffer']);
450
        
451
    }
452
453
    /**
454
     * Set the components.
455
     *
456
     * @return void
457
     */
458
    protected function setComponents(): void
459
    {
460
        $componentConfig = [
461
            'Ip' => $this->getOption('ip', 'components'),
462
            'Rdns' => $this->getOption('rdns', 'components'),
463
            'Header' => $this->getOption('header', 'components'),
464
            'UserAgent' => $this->getOption('user_agent', 'components'),
465
            'TrustedBot' => $this->getOption('trusted_bot', 'components'),
466
        ];
467
468
        foreach ($componentConfig as $className => $config) {
469
            $class = 'Shieldon\Firewall\Component\\' . $className;
470
471
            if ($config['enable']) {
472
                $componentInstance = new $class();
473
474
                if ($className === 'Ip') {
475
                    $this->kernel->add($componentInstance);
476
477
                    // Need Ip component to be loaded before calling this method.
478
                    $this->applyComponentIpManager();
479
                    
480
                } elseif ($config['strict_mode']) {
481
                    $componentInstance->setStrict(true);
482
                    $this->kernel->add($componentInstance);
483
                }
484
            }
485
        }
486
    }
487
488
    /**
489
     * Set the Captcha modules.
490
     *
491
     * @return void
492
     */
493
    protected function setCaptchas(): void
494
    {
495
        $recaptchaSetting = $this->getOption('recaptcha', 'captcha_modules');
496
        $imageSetting = $this->getOption('image', 'captcha_modules');
497
498
        if ($recaptchaSetting['enable']) {
499
500
            $googleRecaptcha = [
501
                'key'     => $recaptchaSetting['config']['site_key'],
502
                'secret'  => $recaptchaSetting['config']['secret_key'],
503
                'version' => $recaptchaSetting['config']['version'],
504
                'lang'    => $recaptchaSetting['config']['lang'],
505
            ];
506
507
            $this->kernel->add(new Captcha\Recaptcha($googleRecaptcha));
508
        }
509
510
        if ($imageSetting['enable']) {
511
512
            $type = $imageSetting['config']['type'] ?? 'alnum';
513
            $length = $imageSetting['config']['length'] ?? 8;
514
515
            switch ($type) {
516
                case 'numeric':
517
                    $imageCaptchaConfig['pool'] = '0123456789';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$imageCaptchaConfig was never initialized. Although not strictly required by PHP, it is generally a good practice to add $imageCaptchaConfig = array(); before regardless.
Loading history...
518
                    break;
519
520
                case 'alpha':
521
                    $imageCaptchaConfig['pool'] = '0123456789abcdefghijklmnopqrstuvwxyz';
522
                    break;
523
524
                case 'alnum':
525
                default:
526
                    $imageCaptchaConfig['pool'] = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
527
            }
528
529
            $imageCaptchaConfig['word_length'] = $length;
530
531
            $this->kernel->add(new Captcha\ImageCaptcha($imageCaptchaConfig));
532
        }
533
    }
534
535
    /**
536
     * Set the messenger modules.
537
     *
538
     * @return void
539
     */
540
    protected function setMessengers(): void
541
    {
542
        // // The ID list of the messenger modules.
543
        $messengerList = [
544
            'telegram',
545
            'line_notify',
546
            'sendgrid',
547
            'native_php_mail',
548
            'smtp',
549
            'mailgun',
550
            'rocket_chat',
551
            'slack',
552
            'slack_webhook',
553
        ];
554
555
        foreach ($messengerList as $messenger) {
556
            $setting = $this->getOption($messenger, 'messengers');
557
558
            if (is_array($setting)) {
559
560
                // Initialize messenger instances from the factory/
561
                if (MessengerFactory::check($messenger, $setting)) {
562
    
563
                    $this->kernel->add(
564
                        MessengerFactory::getInstance(
565
                            // The ID of the messenger module in the configuration.
566
                            $messenger, 
567
                            // The settings of the messenger module in the configuration.
568
                            $setting    
569
                        )
570
                    );
571
                }
572
            }
573
574
            unset($setting);
575
        }
576
    }
577
578
    /**
579
     * Set message events.
580
     *
581
     * @return void
582
     */
583
    protected function setMessageEvents(): void
584
    {
585
        $setting = $this->getOption('failed_attempts_in_a_row', 'events');
586
587
        $notifyDataCircle = false;
588
        $notifySystemFirewall = false;
589
590
        if ($setting['data_circle']['messenger']) {
591
            $notifyDataCircle = true;
592
        }
593
594
        if ($setting['system_firewall']['messenger']) {
595
            $notifyDataCircle = true;
596
        }
597
598
        $this->kernel->setProperty('deny_attempt_notify', [
599
            'data_circle' => $notifyDataCircle,
600
            'system_firewall' => $notifySystemFirewall,
601
        ]);
602
    }
603
604
    /**
605
     * Set deny attempts.
606
     *
607
     * @return void
608
     */
609
    protected function setDenyAttempts(): void
610
    {
611
        $setting = $this->getOption('failed_attempts_in_a_row', 'events');
612
613
        $enableDataCircle = false;
614
        $enableSystemFirewall = false;
615
616
        if ($setting['data_circle']['enable']) {
617
            $enableDataCircle = true;
618
        }
619
620
        if ($setting['system_firewall']['enable']) {
621
            $enableSystemFirewall = true;
622
        }
623
624
        $this->kernel->setProperty('deny_attempt_enable', [
625
            'data_circle' => $enableDataCircle,
626
            'system_firewall' => $enableSystemFirewall,
627
        ]);
628
629
        $this->kernel->setProperty('deny_attempt_buffer', [
630
            'data_circle' => $setting['data_circle']['buffer'] ?? 10,
631
            'system_firewall' => $setting['data_circle']['buffer'] ?? 10,
632
        ]);
633
634
        // Check the time of the last failed attempt. @since 0.2.0
635
        $recordAttempt = $this->getOption('record_attempt');
636
637
        $detectionPeriod = $recordAttempt['detection_period'] ?? 5;
638
        $timeToReset = $recordAttempt['time_to_reset'] ?? 1800;
639
640
        $this->kernel->setProperty('record_attempt_detection_period', $detectionPeriod);
641
        $this->kernel->setProperty('reset_attempt_counter', $timeToReset);
642
    }
643
644
    /**
645
     * Set iptables working folder.
646
     *
647
     * @return void
648
     */
649
    protected function setIptablesWatchingFolder(): void
650
    {
651
        $iptablesSetting = $this->getOption('config', 'iptables');
652
        $this->kernel->setProperty('iptables_watching_folder',  $iptablesSetting['watching_folder']);
653
    }
654
655
    /**
656
     * Set the online session limit.
657
     *
658
     * @return void
659
     */
660
    protected function setSessionLimit(): void
661
    {
662
        $sessionLimitSetting = $this->getOption('online_session_limit');
663
664
        if ($sessionLimitSetting['enable']) {
665
666
            $onlineUsers = $sessionLimitSetting['config']['count'] ?? 100;
667
            $alivePeriod = $sessionLimitSetting['config']['period'] ?? 300;
668
669
            $this->kernel->limitSession($onlineUsers, $alivePeriod);
670
        }
671
    }
672
673
    /**
674
     * Set the cron job.
675
     * This is triggered by the pageviews, not system cron job.
676
     *
677
     * @return void
678
     */
679
    protected function setCronJob(): void 
680
    {
681
        $cronjobSetting = $this->getOption('reset_circle', 'cronjob');
682
683
        if ($cronjobSetting['enable']) {
684
685
            $nowTime = time();
686
687
            $lastResetTime = $cronjobSetting['config']['last_update'];
688
689
            if (!empty($lastResetTime) ) {
690
                $lastResetTime = strtotime($lastResetTime);
691
            } else {
692
                // @codeCoverageIgnoreStart
693
                $lastResetTime = strtotime(date('Y-m-d 00:00:00'));
694
                // @codeCoverageIgnoreEnd
695
            }
696
697
            if (($nowTime - $lastResetTime) > $cronjobSetting['config']['period']) {
698
699
                $updateResetTime = date('Y-m-d 00:00:00');
700
701
                // Update new reset time.
702
                $this->setConfig('cronjob.reset_circle.config.last_update', $updateResetTime);
703
                $this->updateConfig();
704
705
                // Remove all logs.
706
                $this->kernel->driver->rebuild();
707
            }
708
        }
709
    }
710
711
    /**
712
     * Set the URLs that want to be excluded from Shieldon protection.
713
     *
714
     * @return void
715
     */
716
    protected function setExcludedUrls(): void
717
    {
718
        $excludedUrls = $this->getOption('excluded_urls');
719
720
        if (!empty($excludedUrls)) {
721
            $list = array_column($excludedUrls, 'url');
722
723
            $this->kernel->setExcludedUrls($list);
724
        }
725
    }
726
727
728
729
    /**
730
     * WWW-Athentication.
731
     *
732
     * @return void
733
     */
734
    protected function setAuthentication(): void
735
    {
736
        $authenticateList = $this->getOption('www_authenticate');
737
738
        if (is_array($authenticateList)) {
739
            $this->add(new Middleware\httpAuthentication($authenticateList));
740
        }
741
    }
742
743
    /**
744
     * Apply the denied list and the allowed list to Ip Component.
745
     */
746
    protected function applyComponentIpManager()
747
    {
748
        $ipList = $this->getOption('ip_manager');
749
750
        $allowedList = [];
751
        $deniedList = [];
752
753
        if (!empty($ipList)) {
754
            foreach ($ipList as $ip) {
755
756
                if (0 === strpos($this->kernel->getCurrentUrl(), $ip['url']) ) {
757
    
758
                    if ('allow' === $ip['rule']) {
759
                        $allowedList[] = $ip['ip'];
760
                    }
761
    
762
                    if ('deny' === $ip['rule']) {
763
                        $deniedList[] = $ip['ip'];
764
                    }
765
                }
766
            }
767
        }
768
769
        if (!empty($allowedList)) {
770
            $this->kernel->component['Ip']->setAllowedItems($allowedList);
771
        }
772
773
        if (!empty($deniedList)) {
774
            $this->kernel->component['Ip']->setDeniedItems($deniedList);
775
        }
776
    }
777
778
    /**
779
     * Set dialog UI.
780
     *
781
     * @return void
782
     */
783
    protected function setDialogUI()
784
    {
785
        $ui = $this->getOption('dialog_ui');
786
787
        if (!empty($ui)) {
788
            get_session()->set('shieldon_ui_lang', $ui['lang']);
789
            $this->kernel->setDialogUI($this->getOption('dialog_ui'));
790
        }
791
    }
792
793
  
794
}
795