Passed
Push — 2.x ( 3c49c1...b272d3 )
by Terry
02:18
created

TemplateTrait::displayPerformanceReport()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
c 0
b 0
f 0
dl 0
loc 22
rs 9.8666
cc 4
nc 5
nop 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\Kernel;
24
25
use Psr\Http\Message\ResponseInterface;
26
use Shieldon\Firewall\Kernel;
27
use Shieldon\Firewall\HttpFactory;
28
use Shieldon\Firewall\Container;
29
use Shieldon\Event\Event;
30
use function Shieldon\Firewall\get_response;
31
use function Shieldon\Firewall\get_request;
32
use function Shieldon\Firewall\get_session_instance;
33
use function Shieldon\Firewall\__;
34
use InvalidArgumentException;
35
use RuntimeException;
36
use function array_keys;
37
use function define;
38
use function defined;
39
use function is_dir;
40
use function ob_end_clean;
41
use function ob_get_contents;
42
use function ob_start;
43
use function file_exists;
44
use function sprintf;
45
46
/*
47
 * The template-related functions.
48
 */
49
trait TemplateTrait
50
{
51
    /**
52
     *   Public methods       | Desctiotion
53
     *  ----------------------|---------------------------------------------
54
     *   respond              | Respond the result.
55
     *   setTemplateDirectory | Set the frontend template directory.
56
     *   getJavascript        | Print a JavaScript snippet in the pages.
57
     *  ----------------------|---------------------------------------------
58
     */
59
60
    /**
61
     * The directory in where the frontend template files are placed.
62
     *
63
     * @var string
64
     */
65
    protected $templateDirectory = '';
66
67
    /**
68
     * Custom dialog UI settings.
69
     *
70
     * @var array
71
     */
72
    protected $dialog = [];
73
74
    /**
75
     * The reason code of a user to be allowed or denied.
76
     *
77
     * @var int|null
78
     */
79
    protected $reason;
80
81
    /**
82
     * Get current visior's path.
83
     *
84
     * @return string
85
     */
86
    abstract public function getCurrentUrl(): string;
87
88
    /**
89
     * Customize the dialog UI.
90
     * 
91
     * @param array $settings The dialog UI settings.
92
     *
93
     * @return void
94
     */
95
    public function setDialog(array $settings): void
96
    {
97
        $this->dialog = $settings;
98
    }
99
100
    /**
101
     * Respond the result.
102
     *
103
     * @return ResponseInterface
104
     */
105
    public function respond(): ResponseInterface
106
    {
107
        $response = get_response();
108
109
        $httpStatusCodes = [
110
            Kernel::RESPONSE_TEMPORARILY_DENY => [
111
                'type' => 'captcha',
112
                'code' => Kernel::HTTP_STATUS_FORBIDDEN,
113
            ],
114
115
            Kernel::RESPONSE_LIMIT_SESSION => [
116
                'type' => 'session_limitation',
117
                'code' => Kernel::HTTP_STATUS_TOO_MANY_REQUESTS,
118
            ],
119
120
            Kernel::RESPONSE_DENY => [
121
                'type' => 'rejection',
122
                'code' => Kernel::HTTP_STATUS_BAD_REQUEST,
123
            ],
124
        ];
125
126
        // Nothing happened. Return.
127
        if (empty($httpStatusCodes[$this->result])) {
128
            return $response;
129
        }
130
131
        $type = $httpStatusCodes[$this->result]['type'];
132
        $statusCode = $httpStatusCodes[$this->result]['code'];
133
134
        $viewPath = $this->getTemplate($type);
135
136
        // The language of output UI. It is used on views.
137
        $langCode = get_session_instance()->get('shieldon_ui_lang') ?? 'en';
138
139
        $onlineinfo = [];
140
        $onlineinfo['queue'] = $this->sessionStatus['queue'];
141
        $onlineinfo['count'] = $this->sessionStatus['count'];
142
        $onlineinfo['period'] = $this->sessionLimit['period'];
143
144
        $dialoguserinfo = [];
145
        $dialoguserinfo['ip'] = $this->ip;
146
        $dialoguserinfo['rdns'] = $this->rdns;
147
        $dialoguserinfo['user_agent'] = get_request()->getHeaderLine('user-agent');
148
149
        // Captcha form
150
        $form = $this->getCurrentUrl();
151
        $captchas = $this->captcha;
152
153
        // Check and confirm the UI settings.
154
        $ui = $this->confirmUiSettings();
155
        $uiInfo = $this->confirmUiInfoSettings($statusCode);
156
157
        $css = include $this->getTemplate('css/default');
158
159
        /**
160
         * Hook - dialog_output
161
         */
162
        Event::doDispatch('dialog_output');
163
164
        $performanceReport = $this->displayPerformanceReport();
165
166
        ob_start();
167
        include $viewPath;
168
        $output = ob_get_contents();
169
        ob_end_clean();
170
171
        // Remove unused variable notices generated from PHP intelephense.
172
        unset($css, $ui, $form, $captchas, $langCode, $performanceReport, $uiInfo);
173
174
        $stream = HttpFactory::createStream();
175
        $stream->write($output);
176
        $stream->rewind();
177
178
        return $response
179
            ->withHeader('X-Protected-By', 'shieldon.io')
180
            ->withBody($stream)
181
            ->withStatus($statusCode);
182
    }
183
184
    /**
185
     * Confirm the UI settings.
186
     *
187
     * @return array
188
     */
189
    private function confirmUiSettings(): array
190
    {
191
        if (!defined('SHIELDON_VIEW')) {
192
            define('SHIELDON_VIEW', true);
193
        }
194
195
        $ui = [
196
            'background_image' => '',
197
            'bg_color'         => '#ffffff',
198
            'header_bg_color'  => '#212531',
199
            'header_color'     => '#ffffff',
200
            'shadow_opacity'   => '0.2',
201
        ];
202
203
        foreach (array_keys($ui) as $key) {
204
            if (!empty($this->dialog[$key])) {
205
                $ui[$key] = $this->dialog[$key];
206
            }
207
        }
208
209
        return $ui;
210
    }
211
212
    /**
213
     * Confirm UI information settings.
214
     * 
215
     * @param int $statusCode HTTP status code.
216
     *
217
     * @return array
218
     */
219
    private function confirmUiInfoSettings(int $statusCode): array
220
    {
221
        $uiInfo = [];
222
223
        $reasonCode = $this->reason;
224
225
        $uiInfo['http_status_code'] = $statusCode;
226
        $uiInfo['reason_code']      = $reasonCode;
227
        $uiInfo['reason_text']      = __('core', 'messenger_text_reason_code_' . $reasonCode);
228
229
        $uiInfo['is_display_online_user_amount']  = $this->properties['display_online_info'];
230
        $uiInfo['is_display_user_information']    = $this->properties['display_user_info'];
231
        $uiInfo['is_display_display_http_code']   = $this->properties['display_http_code'];
232
        $uiInfo['is_display_display_reason_code'] = $this->properties['display_reason_code'];
233
        $uiInfo['is_display_display_reason_text'] = $this->properties['display_reason_text'];
234
235
        return $uiInfo;
236
    }
237
238
    /**
239
     * Print a JavaScript snippet in your webpages.
240
     * 
241
     * This snippet generate cookie on client's browser,then we check the 
242
     * cookie to identify the client is a rebot or not.
243
     *
244
     * @return string
245
     */
246
    public function getJavascript(): string
247
    {
248
        $tmpCookieName = $this->properties['cookie_name'];
249
        $tmpCookieDomain = $this->properties['cookie_domain'];
250
251
        if (empty($tmpCookieDomain) && get_request()->getHeaderLine('host')) {
252
            $tmpCookieDomain = get_request()->getHeaderLine('host');
253
        }
254
255
        $tmpCookieValue = $this->properties['cookie_value'];
256
257
        $jsString = '
258
            <script>
259
                var d = new Date();
260
                d.setTime(d.getTime()+(60*60*24*30));
261
                document.cookie = "' . $tmpCookieName . '=' . $tmpCookieValue . ';domain=.' . $tmpCookieDomain . ';expires="+d.toUTCString();
262
            </script>
263
        ';
264
265
        return $jsString;
266
    }
267
268
    /**
269
     * Set the frontend template directory.
270
     *
271
     * @param string $directory The directory in where the template files are placed.
272
     *
273
     * @return void
274
     */
275
    public function setTemplateDirectory(string $directory): void
276
    {
277
        if (!is_dir($directory)) {
278
            throw new InvalidArgumentException(
279
                'The template directory does not exist.'
280
            );
281
        }
282
        $this->templateDirectory = $directory;
283
    }
284
285
    /**
286
     * Get a template PHP file.
287
     *
288
     * @param string $type The template type.
289
     *
290
     * @return string
291
     */
292
    protected function getTemplate(string $type): string
293
    {
294
        $directory = Kernel::KERNEL_DIR . '/../../templates/frontend';
295
296
        if (!empty($this->templateDirectory)) {
297
            $directory = $this->templateDirectory;
298
        }
299
300
        $path = $directory . '/' . $type . '.php';
301
302
        if (!file_exists($path)) {
303
            throw new RuntimeException(
304
                sprintf(
305
                    'The templeate file is missing. (%s)',
306
                    $path
307
                )
308
            );
309
        }
310
311
        return $path;
312
    }
313
314
    /**
315
     * Count the performance statistics.
316
     *
317
     * @return array
318
     */
319
    protected function getPerformanceStats(): array
320
    {
321
        $statStart = Container::get('shieldon_start');
322
        $statEnd = Container::get('shieldon_end');
323
324
        $startTimeArr = explode(' ',$statStart['time']);
325
        $endTimeArr = explode(' ',$statStart['time']);
326
327
        $timeDifference = ($endTimeArr[1] - $startTimeArr[1]) + ($endTimeArr[0] - $startTimeArr[0]);
328
        $memoryDifference = round(($statEnd['memory'] - $statStart['memory']) / 1024, 2); // KB
329
330
        $data = [
331
            'time' => $timeDifference,
332
            'memory' => $memoryDifference,
333
        ];
334
335
        return $data;
336
    }
337
338
    /**
339
     * Display the HTML of the performance report.
340
     *
341
     * @return string
342
     */
343
    protected function displayPerformanceReport(): string
344
    {
345
        if (!Container::get('shieldon_start')) {
346
            return '';
347
        }
348
349
        $html = '';
350
351
        $performance = $this->getPerformanceStats();
352
353
        if ($performance['time'] < 0.001) {
354
            $performance['time'] = 'fewer than 0.001';
355
        }
356
357
        if (isset($performance['time'])) {
358
            $html .= '<div class="performance-report">';
359
            $html .= 'Memory consumed: <strong>' . $performance['memory'] . '</strong> KB / ';
360
            $html .= 'Execution:  <strong>' . $performance['time'] . ' </strong> seconds.';
361
            $html .= '</div>';
362
        }
363
364
        return $html;
365
    }
366
}
367