Passed
Push — 1.0.0-dev ( 459011...73c7ea )
by nguereza
03:08
created

Response   F

Complexity

Total Complexity 63

Size/Duplication

Total Lines 488
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 219
dl 0
loc 488
rs 3.36
c 3
b 0
f 0
wmc 63

20 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 17 7
A sendCacheNotYetExpireInfoToBrowser() 0 17 4
A sendCachePageContentToBrowser() 0 25 3
A getModuleInfoForView() 0 19 6
A sendError() 0 18 3
B renderFinalPage() 0 34 7
A render() 0 30 3
A setHeaders() 0 2 1
A setRequiredHeaders() 0 8 3
A replaceElapseTimeAndMemoryUsage() 0 9 1
A loadView() 0 23 6
A sendHeaders() 0 7 3
A savePageContentIntoCache() 0 28 3
A redirect() 0 10 2
A renderFinalPageFromCache() 0 16 3
A getFinalPageRendered() 0 2 1
A send404() 0 30 3
A setHeader() 0 2 1
A getHeader() 0 5 2
A getHeaders() 0 2 1

How to fix   Complexity   

Complex Class

Complex classes like Response often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Response, and based on these observations, apply Extract Interface, too.

1
<?php
2
    defined('ROOT_PATH') or exit('Access denied');
3
    /**
4
     * TNH Framework
5
     *
6
     * A simple PHP framework using HMVC architecture
7
     *
8
     * This content is released under the GNU GPL License (GPL)
9
     *
10
     * Copyright (C) 2017 Tony NGUEREZA
11
     *
12
     * This program is free software; you can redistribute it and/or
13
     * modify it under the terms of the GNU General Public License
14
     * as published by the Free Software Foundation; either version 3
15
     * of the License, or (at your option) any later version.
16
     *
17
     * This program is distributed in the hope that it will be useful,
18
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
     * GNU General Public License for more details.
21
     *
22
     * You should have received a copy of the GNU General Public License
23
     * along with this program; if not, write to the Free Software
24
     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
25
     */
26
27
    class Response extends BaseStaticClass {
28
29
        /**
30
         * The list of request header to send with response
31
         * @var array
32
         */
33
        private static $headers = array();
34
		
35
        /**
36
         * The final page content to display to user
37
         * @var string
38
         */
39
        private $_pageRender = null;
40
		
41
        /**
42
         * The current request URL
43
         * @var string
44
         */
45
        private $_currentUrl = null;
46
		
47
        /**
48
         * The current request URL cache key
49
         * @var string
50
         */
51
        private $_currentUrlCacheKey = null;
52
		
53
        /**
54
         * Whether we can compress the output using Gzip
55
         * @var boolean
56
         */
57
        private static $_canCompressOutput = false;
58
		
59
        /**
60
         * Construct new instance
61
         */
62
        public function __construct() {
63
            $currentUrl = '';
64
            if (!empty($_SERVER['REQUEST_URI'])) {
65
                $currentUrl = $_SERVER['REQUEST_URI'];
66
            }
67
            if (!empty($_SERVER['QUERY_STRING'])) {
68
                $currentUrl .= '?' . $_SERVER['QUERY_STRING'];
69
            }
70
            $this->_currentUrl = $currentUrl;
71
					
72
            $this->_currentUrlCacheKey = md5($this->_currentUrl);
73
			
74
            self::$_canCompressOutput = get_config('compress_output')
75
                                          && isset($_SERVER['HTTP_ACCEPT_ENCODING']) 
76
                                          && stripos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false 
77
                                          && extension_loaded('zlib')
78
                                          && (bool) ini_get('zlib.output_compression') === false;
79
        }
80
81
		
82
        /**
83
         * Send the HTTP Response headers
84
         * @param  integer $httpCode the HTTP status code
85
         * @param  array   $headers   the additional headers to add to the existing headers list
86
         */
87
        public static function sendHeaders($httpCode = 200, array $headers = array()) {
88
            set_http_status_header($httpCode);
89
            self::setHeaders($headers);
90
            self::setRequiredHeaders();
91
            if (!headers_sent()) {
92
                foreach (self::getHeaders() as $key => $value) {
93
                    header($key . ': ' . $value);
94
                }
95
            }
96
        }
97
98
        /**
99
         * Get the list of the headers
100
         * @return array the headers list
101
         */
102
        public static function getHeaders() {
103
            return self::$headers;
104
        }
105
106
        /**
107
         * Get the header value for the given name
108
         * @param  string $name the header name
109
         * @return string|null       the header value
110
         */
111
        public static function getHeader($name) {
112
            if (array_key_exists($name, self::$headers)) {
113
                return self::$headers[$name];
114
            }
115
            return null;
116
        }
117
118
119
        /**
120
         * Set the header value for the specified name
121
         * @param string $name  the header name
122
         * @param string $value the header value to be set
123
         */
124
        public static function setHeader($name, $value) {
125
            self::$headers[$name] = $value;
126
        }
127
128
        /**
129
         * Set the headers using array
130
         * @param array $headers the list of the headers to set. 
131
         * Note: this will merge with the existing headers
132
         */
133
        public static function setHeaders(array $headers) {
134
            self::$headers = array_merge(self::getHeaders(), $headers);
135
        }
136
		
137
        /**
138
         * Redirect user to the specified page
139
         * @param  string $path the URL or URI to be redirect to
140
         */
141
        public static function redirect($path = '') {
142
            $logger = self::getLogger();
143
            $url = Url::site_url($path);
144
            $logger->info('Redirect to URL [' . $url . ']');
145
            if (!headers_sent()) {
146
                header('Location: ' . $url);
147
                exit;
148
            }
149
            echo '<script>
150
					location.href = "'.$url . '";
151
				</script>';
152
        }
153
154
        /**
155
         * Render the view to display later or return the content
156
         * @param  string  $view   the view name or path
157
         * @param  array|object   $data   the variable data to use in the view
158
         * @param  boolean $return whether to return the view generated content or display it directly
159
         * @return void|string          if $return is true will return the view content otherwise
160
         * will display the view content.
161
         */
162
        public function render($view, $data = null, $return = false) {
163
            $logger = self::getLogger();
164
            //convert data to an array
165
            $data = (array) $data;
166
            $view = str_ireplace('.php', '', $view);
167
            $view = trim($view, '/\\');
168
            $viewFile = $view . '.php';
169
            $path = APPS_VIEWS_PATH . $viewFile;
170
			
171
            //check in module first
172
            $logger->debug('Checking the view [' . $view . '] from module list ...');
173
            $moduleInfo = $this->getModuleInfoForView($view);
174
            $module = $moduleInfo['module'];
175
            $view = $moduleInfo['view'];
176
			
177
            $moduleViewPath = Module::findViewFullPath($view, $module);
178
            if ($moduleViewPath) {
179
                $path = $moduleViewPath;
180
                $logger->info('Found view [' . $view . '] in module [' . $module . '], the file path is [' . $moduleViewPath . '] we will used it');
181
            } else {
182
                $logger->info('Cannot find view [' . $view . '] in module [' . $module . '] using the default location');
183
            }
184
			
185
            $logger->info('The view file path to be loaded is [' . $path . ']');
186
			
187
            /////////
188
            if ($return) {
189
                return $this->loadView($path, $data, true);
190
            }
191
            $this->loadView($path, $data, false);
192
        }
193
194
		
195
        /**
196
         * Send the final page output to user
197
         */
198
        public function renderFinalPage() {
199
            $logger = self::getLogger();
200
            $obj = & get_instance();
201
            $cachePageStatus = get_config('cache_enable', false) && !empty($obj->view_cache_enable);
202
            $dispatcher = $obj->eventdispatcher;
203
            $content = $this->_pageRender;
204
            if (!$content) {
205
                $logger->warning('The final view content is empty.');
206
                return;
207
            }
208
            //dispatch
209
            $event = $dispatcher->dispatch(new EventInfo('FINAL_VIEW_READY', $content, true));
210
            $content = null;
211
            if (!empty($event->payload)) {
212
                $content = $event->payload;
213
            }
214
            if (empty($content)) {
215
                $logger->warning('The view content is empty after dispatch to event listeners.');
216
            }
217
            //check whether need save the page into cache.
218
            if ($cachePageStatus) {
219
                $this->savePageContentIntoCache($content);
220
            }
221
            $content = $this->replaceElapseTimeAndMemoryUsage($content);
222
223
            //compress the output if is available
224
            $type = null;
225
            if (self::$_canCompressOutput) {
226
                $type = 'ob_gzhandler';
227
            }
228
            ob_start($type);
229
            self::sendHeaders(200);
230
            echo $content;
231
            ob_end_flush();
232
        }
233
234
		
235
        /**
236
         * Send the final page output to user if is cached
237
         * @param object $cache the cache instance
238
         *
239
         * @return boolean whether the page content if available or not
240
         */
241
        public function renderFinalPageFromCache(&$cache) {
242
            $logger = self::getLogger();
243
            //the current page cache key for identification
244
            $pageCacheKey = $this->_currentUrlCacheKey;
245
			
246
            $logger->debug('Checking if the page content for the URL [' . $this->_currentUrl . '] is cached ...');
247
            //get the cache information to prepare header to send to browser
248
            $cacheInfo = $cache->getInfo($pageCacheKey);
249
            if ($cacheInfo) {
250
                $status = $this->sendCacheNotYetExpireInfoToBrowser($cacheInfo);
251
                if ($status === false) {
252
                    return $this->sendCachePageContentToBrowser($cache);
253
                }
254
                return true;
255
            }
256
            return false;
257
        }
258
	
259
		
260
        /**
261
         * Get the final page to be rendered
262
         * @return string
263
         */
264
        public function getFinalPageRendered() {
265
            return $this->_pageRender;
266
        }
267
268
        /**
269
         * Send the HTTP 404 error if can not found the 
270
         * routing information for the current request
271
         */
272
        public static function send404() {
273
            /********* for logs **************/
274
            //can't use $obj = & get_instance()  here because the global super object will be available until
275
            //the main controller is loaded even for Loader::library('xxxx');
276
            $logger = self::getLogger();
277
            $request = & class_loader('Request', 'classes');
278
            $userAgent = & class_loader('Browser');
279
            $browser = $userAgent->getPlatform() . ', ' . $userAgent->getBrowser() . ' ' . $userAgent->getVersion();
280
			
281
            //here can't use Loader::functions just include the helper manually
282
            require_once CORE_FUNCTIONS_PATH . 'function_user_agent.php';
283
284
            $str = '[404 page not found] : ';
285
            $str .= ' Unable to find the request page [' . $request->requestUri() . ']. The visitor IP address [' . get_ip() . '], browser [' . $browser . ']';
286
            $logger->error($str);
287
            /***********************************/
288
            $path = CORE_VIEWS_PATH . '404.php';
289
            if (file_exists($path)) {
290
                //compress the output if is available
291
                $type = null;
292
                if (self::$_canCompressOutput) {
293
                    $type = 'ob_gzhandler';
294
                }
295
                ob_start($type);
296
                require_once $path;
297
                $output = ob_get_clean();
298
                self::sendHeaders(404);
299
                echo $output;
300
            } else {
301
                show_error('The 404 view [' . $path . '] does not exist');
302
            }
303
        }
304
305
        /**
306
         * Display the error to user
307
         * @param  array  $data the error information
308
         */
309
        public static function sendError(array $data = array()) {
310
            $path = CORE_VIEWS_PATH . 'errors.php';
311
            if (file_exists($path)) {
312
                //compress the output if is available
313
                $type = null;
314
                if (self::$_canCompressOutput) {
315
                    $type = 'ob_gzhandler';
316
                }
317
                ob_start($type);
318
                extract($data);
319
                require_once $path;
320
                $output = ob_get_clean();
321
                self::sendHeaders(503);
322
                echo $output;
323
            } else {
324
                //can't use show_error() at this time because some dependencies not yet loaded and to prevent loop
325
                set_http_status_header(503);
326
                echo 'The error view [' . $path . '] does not exist';
327
            }
328
        }
329
330
        /**
331
         * Send the cache not yet expire to browser
332
         * @param  array $cacheInfo the cache information
333
         * @return boolean            true if the information is sent otherwise false
334
         */
335
        protected function sendCacheNotYetExpireInfoToBrowser($cacheInfo) {
336
            if (!empty($cacheInfo)) {
337
                $logger = self::getLogger();
338
                $lastModified = $cacheInfo['mtime'];
339
                $expire = $cacheInfo['expire'];
340
                $maxAge = $expire - $_SERVER['REQUEST_TIME'];
341
                self::setHeader('Pragma', 'public');
342
                self::setHeader('Cache-Control', 'max-age=' . $maxAge . ', public');
343
                self::setHeader('Expires', gmdate('D, d M Y H:i:s', $expire) . ' GMT');
344
                self::setHeader('Last-modified', gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
345
                if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $lastModified <= strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
346
                    $logger->info('The cache page content is not yet expire for the URL [' . $this->_currentUrl . '] send 304 header to browser');
347
                    self::sendHeaders(304);
348
                    return true;
349
                }
350
            }
351
            return false;
352
        }
353
354
        /**
355
         * Set the value of '{elapsed_time}' and '{memory_usage}'
356
         * @param  string $content the page content
357
         * @return string          the page content after replace 
358
         * '{elapsed_time}', '{memory_usage}'
359
         */
360
        protected function replaceElapseTimeAndMemoryUsage($content) {
361
            //load benchmark class
362
            $benchmark = & class_loader('Benchmark');
363
			
364
            // Parse out the elapsed time and memory usage,
365
            // then swap the pseudo-variables with the data
366
            $elapsedTime = $benchmark->elapsedTime('APP_EXECUTION_START', 'APP_EXECUTION_END');
367
            $memoryUsage	= round($benchmark->memoryUsage('APP_EXECUTION_START', 'APP_EXECUTION_END') / 1024 / 1024, 6) . 'MB';
368
            return str_replace(array('{elapsed_time}', '{memory_usage}'), array($elapsedTime, $memoryUsage), $content);	
369
        }
370
371
        /**
372
         * Send the page content from cache to browser
373
         * @param object $cache the cache instance
374
         * @return boolean     the status of the operation
375
         */
376
        protected function sendCachePageContentToBrowser(&$cache) {
377
            $logger = self::getLogger();
378
            $logger->info('The cache page content is expired or the browser does not send the HTTP_IF_MODIFIED_SINCE header for the URL [' . $this->_currentUrl . '] send cache headers to tell the browser');
379
            self::sendHeaders(200);
380
            //current page cache key
381
            $pageCacheKey = $this->_currentUrlCacheKey;
382
            //get the cache content
383
            $content = $cache->get($pageCacheKey);
384
            if ($content) {
385
                $logger->info('The page content for the URL [' . $this->_currentUrl . '] already cached just display it');
386
                $content = $this->replaceElapseTimeAndMemoryUsage($content);
387
                ///display the final output
388
                //compress the output if is available
389
                $type = null;
390
                if (self::$_canCompressOutput) {
391
                    $type = 'ob_gzhandler';
392
                }
393
                ob_start($type);
394
                echo $content;
395
                ob_end_flush();
396
                return true;
397
            }
398
            $logger->info('The page cache content for the URL [' . $this->_currentUrl . '] is not valid may be already expired');
399
            $cache->delete($pageCacheKey);
400
            return false;
401
        }
402
403
        /**
404
         * Save the content of page into cache
405
         * @param  string $content the page content to be saved
406
         * @return void
407
         */
408
        protected function savePageContentIntoCache($content) {
409
            $obj = & get_instance();
410
            $logger = self::getLogger();
411
412
            //current page URL
413
            $url = $this->_currentUrl;
414
            //Cache view Time to live in second
415
            $viewCacheTtl = get_config('cache_ttl');
416
            if (!empty($obj->view_cache_ttl)) {
417
                $viewCacheTtl = $obj->view_cache_ttl;
418
            }
419
            //the cache handler instance
420
            $cacheInstance = $obj->cache;
421
            //the current page cache key for identification
422
            $cacheKey = $this->_currentUrlCacheKey;
423
            $logger->debug('Save the page content for URL [' . $url . '] into the cache ...');
424
            $cacheInstance->set($cacheKey, $content, $viewCacheTtl);
425
			
426
            //get the cache information to prepare header to send to browser
427
            $cacheInfo = $cacheInstance->getInfo($cacheKey);
428
            if ($cacheInfo) {
429
                $lastModified = $cacheInfo['mtime'];
430
                $expire = $cacheInfo['expire'];
431
                $maxAge = $expire - time();
432
                self::setHeader('Pragma', 'public');
433
                self::setHeader('Cache-Control', 'max-age=' . $maxAge . ', public');
434
                self::setHeader('Expires', gmdate('D, d M Y H:i:s', $expire) . ' GMT');
435
                self::setHeader('Last-modified', gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');	
436
            }
437
        }
438
		
439
440
        /**
441
         * Get the module information for the view to load
442
         * @param  string $view the view name like moduleName/viewName, viewName
443
         * 
444
         * @return array        the module information
445
         * array(
446
         * 	'module'=> 'module_name'
447
         * 	'view' => 'view_name'
448
         * 	'viewFile' => 'view_file'
449
         * )
450
         */
451
        protected  function getModuleInfoForView($view) {
452
            $module = null;
453
            $viewFile = null;
454
            $obj = & get_instance();
455
            //check if the request class contains module name
456
            $viewPath = explode('/', $view);
457
            if (count($viewPath) >= 2 && isset($viewPath[0]) && in_array($viewPath[0], Module::getModuleList())) {
458
                $module = $viewPath[0];
459
                array_shift($viewPath);
460
                $view = implode('/', $viewPath);
461
                $viewFile = $view . '.php';
462
            }
463
            if (!$module && !empty($obj->moduleName)) {
464
                $module = $obj->moduleName;
465
            }
466
            return array(
467
                        'view' => $view,
468
                        'module' => $module,
469
                        'viewFile' => $viewFile
470
                    );
471
        }
472
473
        /**
474
         * Render the view page
475
         * @see  Response::render
476
         * @return void|string
477
         */
478
        protected  function loadView($path, array $data = array(), $return = false) {
479
            $found = false;
480
            if (file_exists($path)) {
481
                //super instance
482
                $obj = & get_instance();
483
                foreach (get_object_vars($obj) as $key => $value) {
484
                    if (!isset($this->{$key})) {
485
                        $this->{$key} = & $obj->{$key};
486
                    }
487
                }
488
                ob_start();
489
                extract($data);
490
                //need use require() instead of require_once because can load this view many time
491
                require $path;
492
                $content = ob_get_clean();
493
                if ($return) {
494
                    return $content;
495
                }
496
                $this->_pageRender .= $content;
497
                $found = true;
498
            }
499
            if (!$found) {
500
                show_error('Unable to find view [' . $path . ']');
501
            }
502
        }
503
504
         /**
505
         * Set the mandory headers, like security, etc.
506
         */
507
        protected static function setRequiredHeaders() {
508
            $requiredHeaders = array(
509
                                'X-XSS-Protection' => '1; mode=block',
510
                                'X-Frame-Options'  => 'SAMEORIGIN'
511
                            );
512
            foreach ($requiredHeaders as $key => $value) {
513
               if (!isset(self::$headers[$key])) {
514
                    self::$headers[$key] = $value;
515
               } 
516
            }
517
        }
518
    }
519