Completed
Push — master ( 64aab5...1b53bc )
by Kenji
04:43 queued 01:56
created

CI_Output::set_cache_header()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 15
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 2
nop 2
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 51 and the first side effect is on line 38.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * CodeIgniter
4
 *
5
 * An open source application development framework for PHP
6
 *
7
 * This content is released under the MIT License (MIT)
8
 *
9
 * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
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
 * @package	CodeIgniter
30
 * @author	EllisLab Dev Team
31
 * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
32
 * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
33
 * @license	http://opensource.org/licenses/MIT	MIT License
34
 * @link	https://codeigniter.com
35
 * @since	Version 1.0.0
36
 * @filesource
37
 */
38
defined('BASEPATH') OR exit('No direct script access allowed');
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
39
40
/**
41
 * Output Class
42
 *
43
 * Responsible for sending final output to the browser.
44
 *
45
 * @package		CodeIgniter
46
 * @subpackage	Libraries
47
 * @category	Output
48
 * @author		EllisLab Dev Team
49
 * @link		https://codeigniter.com/user_guide/libraries/output.html
50
 */
51
class CI_Output {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
52
53
	/**
54
	 * Final output string
55
	 *
56
	 * @var	string
57
	 */
58
	public $final_output;
59
60
	/**
61
	 * Cache expiration time
62
	 *
63
	 * @var	int
64
	 */
65
	public $cache_expiration = 0;
66
67
	/**
68
	 * List of server headers
69
	 *
70
	 * @var	array
71
	 */
72
	public $headers = array();
73
74
	/**
75
	 * List of mime types
76
	 *
77
	 * @var	array
78
	 */
79
	public $mimes =	array();
80
81
	/**
82
	 * Mime-type for the current page
83
	 *
84
	 * @var	string
85
	 */
86
	protected $mime_type = 'text/html';
87
88
	/**
89
	 * Enable Profiler flag
90
	 *
91
	 * @var	bool
92
	 */
93
	public $enable_profiler = FALSE;
94
95
	/**
96
	 * php.ini zlib.output_compression flag
97
	 *
98
	 * @var	bool
99
	 */
100
	protected $_zlib_oc = FALSE;
101
102
	/**
103
	 * CI output compression flag
104
	 *
105
	 * @var	bool
106
	 */
107
	protected $_compress_output = FALSE;
108
109
	/**
110
	 * List of profiler sections
111
	 *
112
	 * @var	array
113
	 */
114
	protected $_profiler_sections =	array();
115
116
	/**
117
	 * Parse markers flag
118
	 *
119
	 * Whether or not to parse variables like {elapsed_time} and {memory_usage}.
120
	 *
121
	 * @var	bool
122
	 */
123
	public $parse_exec_vars = TRUE;
124
125
	/**
126
	 * mbstring.func_overload flag
127
	 *
128
	 * @var	bool
129
	 */
130
	protected static $func_overload;
131
132
	/**
133
	 * Response status (status code, text and is redirection?)
134
	 *
135
	 * @var array
136
	 *
137
	 * added by ci-phpunit-test
138
	 */
139
	public $_status;
140
141
	/**
142
	 * Cookies
143
	 *
144
	 * @var array
145
	 *
146
	 * added by ci-phpunit-test
147
	 */
148
	public $_cookies;
149
150
	/**
151
	 * Class constructor
152
	 *
153
	 * Determines whether zLib output compression will be used.
154
	 *
155
	 * @return	void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
156
	 */
157
	public function __construct()
158
	{
159
		$this->_zlib_oc = (bool) ini_get('zlib.output_compression');
160
		$this->_compress_output = (
161
			$this->_zlib_oc === FALSE
162
			&& config_item('compress_output') === TRUE
163
			&& extension_loaded('zlib')
164
		);
165
166
		isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload'));
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
167
168
		// Get mime types for later
169
		$this->mimes =& get_mimes();
170
171
		log_message('info', 'Output Class Initialized');
172
	}
173
174
	// --------------------------------------------------------------------
175
176
	/**
177
	 * Get Output
178
	 *
179
	 * Returns the current output string.
180
	 *
181
	 * @return	string
182
	 */
183
	public function get_output()
184
	{
185
		return $this->final_output;
186
	}
187
188
	// --------------------------------------------------------------------
189
190
	/**
191
	 * Set Output
192
	 *
193
	 * Sets the output string.
194
	 *
195
	 * @param	string	$output	Output data
196
	 * @return	CI_Output
197
	 */
198
	public function set_output($output)
199
	{
200
		$this->final_output = $output;
201
		return $this;
202
	}
203
204
	// --------------------------------------------------------------------
205
206
	/**
207
	 * Append Output
208
	 *
209
	 * Appends data onto the output string.
210
	 *
211
	 * @param	string	$output	Data to append
212
	 * @return	CI_Output
213
	 */
214
	public function append_output($output)
215
	{
216
		$this->final_output .= $output;
217
		return $this;
218
	}
219
220
	// --------------------------------------------------------------------
221
222
	/**
223
	 * Set Header
224
	 *
225
	 * Lets you set a server header which will be sent with the final output.
226
	 *
227
	 * Note: If a file is cached, headers will not be sent.
228
	 * @todo	We need to figure out how to permit headers to be cached.
229
	 *
230
	 * @param	string	$header		Header
231
	 * @param	bool	$replace	Whether to replace the old header value, if already set
232
	 * @return	CI_Output
233
	 */
234
	public function set_header($header, $replace = TRUE)
235
	{
236
		// If zlib.output_compression is enabled it will compress the output,
237
		// but it will not modify the content-length header to compensate for
238
		// the reduction, causing the browser to hang waiting for more data.
239
		// We'll just skip content-length in those cases.
240
		if ($this->_zlib_oc && strncasecmp($header, 'content-length', 14) === 0)
241
		{
242
			return $this;
243
		}
244
245
		$this->headers[] = array($header, $replace);
246
		return $this;
247
	}
248
249
	// --------------------------------------------------------------------
250
251
	/**
252
	 * Set Content-Type Header
253
	 *
254
	 * @param	string	$mime_type	Extension of the file we're outputting
255
	 * @param	string	$charset	Character set (default: NULL)
256
	 * @return	CI_Output
257
	 */
258
	public function set_content_type($mime_type, $charset = NULL)
259
	{
260
		if (strpos($mime_type, '/') === FALSE)
261
		{
262
			$extension = ltrim($mime_type, '.');
263
264
			// Is this extension supported?
265
			if (isset($this->mimes[$extension]))
266
			{
267
				$mime_type =& $this->mimes[$extension];
268
269
				if (is_array($mime_type))
270
				{
271
					$mime_type = current($mime_type);
272
				}
273
			}
274
		}
275
276
		$this->mime_type = $mime_type;
277
278
		if (empty($charset))
279
		{
280
			$charset = config_item('charset');
281
		}
282
283
		$header = 'Content-Type: '.$mime_type
284
			.(empty($charset) ? '' : '; charset='.$charset);
285
286
		$this->headers[] = array($header, TRUE);
287
		return $this;
288
	}
289
290
	// --------------------------------------------------------------------
291
292
	/**
293
	 * Get Current Content-Type Header
294
	 *
295
	 * @return	string	'text/html', if not already set
296
	 */
297
	public function get_content_type()
298
	{
299
		for ($i = 0, $c = count($this->headers); $i < $c; $i++)
300
		{
301
			if (sscanf($this->headers[$i][0], 'Content-Type: %[^;]', $content_type) === 1)
302
			{
303
				return $content_type;
304
			}
305
		}
306
307
		return 'text/html';
308
	}
309
310
	// --------------------------------------------------------------------
311
312
	/**
313
	 * Get Header
314
	 *
315
	 * @param	string	$header
316
	 * @return	string
317
	 */
318
	public function get_header($header)
319
	{
320
		// Combine headers already sent with our batched headers
321
		$headers = array_merge(
322
			// We only need [x][0] from our multi-dimensional array
323
			array_map('array_shift', $this->headers),
324
			headers_list()
325
		);
326
327
		if (empty($headers) OR empty($header))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
328
		{
329
			return NULL;
330
		}
331
332
		// Count backwards, in order to get the last matching header
333
		for ($c = count($headers) - 1; $c > -1; $c--)
334
		{
335
			if (strncasecmp($header, $headers[$c], $l = self::strlen($header)) === 0)
336
			{
337
				return trim(self::substr($headers[$c], $l+1));
338
			}
339
		}
340
341
		return NULL;
342
	}
343
344
	// --------------------------------------------------------------------
345
346
	/**
347
	 * Set HTTP Status Header
348
	 *
349
	 * As of version 1.7.2, this is an alias for common function
350
	 * set_status_header().
351
	 *
352
	 * @param	int	$code	Status code (default: 200)
353
	 * @param	string	$text	Optional message
354
	 * @return	CI_Output
355
	 */
356
	public function set_status_header($code = 200, $text = '')
357
	{
358
		set_status_header($code, $text);
359
		return $this;
360
	}
361
362
	// --------------------------------------------------------------------
363
364
	/**
365
	 * Enable/disable Profiler
366
	 *
367
	 * @param	bool	$val	TRUE to enable or FALSE to disable
368
	 * @return	CI_Output
369
	 */
370
	public function enable_profiler($val = TRUE)
371
	{
372
		$this->enable_profiler = is_bool($val) ? $val : TRUE;
373
		return $this;
374
	}
375
376
	// --------------------------------------------------------------------
377
378
	/**
379
	 * Set Profiler Sections
380
	 *
381
	 * Allows override of default/config settings for
382
	 * Profiler section display.
383
	 *
384
	 * @param	array	$sections	Profiler sections
385
	 * @return	CI_Output
386
	 */
387
	public function set_profiler_sections($sections)
388
	{
389
		if (isset($sections['query_toggle_count']))
390
		{
391
			$this->_profiler_sections['query_toggle_count'] = (int) $sections['query_toggle_count'];
392
			unset($sections['query_toggle_count']);
393
		}
394
395
		foreach ($sections as $section => $enable)
396
		{
397
			$this->_profiler_sections[$section] = ($enable !== FALSE);
398
		}
399
400
		return $this;
401
	}
402
403
	// --------------------------------------------------------------------
404
405
	/**
406
	 * Set Cache
407
	 *
408
	 * @param	int	$time	Cache expiration time in minutes
409
	 * @return	CI_Output
410
	 */
411
	public function cache($time)
412
	{
413
		$this->cache_expiration = is_numeric($time) ? $time : 0;
0 ignored issues
show
Documentation Bug introduced by
It seems like is_numeric($time) ? $time : 0 can also be of type double or string. However, the property $cache_expiration is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
414
		return $this;
415
	}
416
417
	// --------------------------------------------------------------------
418
419
	/**
420
	 * Display Output
421
	 *
422
	 * Processes and sends finalized output data to the browser along
423
	 * with any server headers and profile data. It also stops benchmark
424
	 * timers so the page rendering speed and memory usage can be shown.
425
	 *
426
	 * Note: All "view" data is automatically put into $this->final_output
427
	 *	 by controller class.
428
	 *
429
	 * @uses	CI_Output::$final_output
430
	 * @param	string	$output	Output data override
431
	 * @return	void
432
	 *
433
	 * modified by ci-phpunit-test
434
	 */
435
	public function _display($output = '')
0 ignored issues
show
Coding Style introduced by
_display uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
436
	{
437
		// Note:  We use load_class() because we can't use $CI =& get_instance()
438
		// since this function is sometimes called by the caching mechanism,
439
		// which happens before the CI super object is available.
440
		$BM =& load_class('Benchmark', 'core');
441
		$CFG =& load_class('Config', 'core');
442
443
		// Grab the super object if we can.
444
		if (class_exists('CI_Controller', FALSE))
445
		{
446
			$CI =& get_instance();
447
		}
448
449
		// --------------------------------------------------------------------
450
451
		// Set the output data
452
		if ($output === '')
453
		{
454
			$output =& $this->final_output;
455
		}
456
457
		// --------------------------------------------------------------------
458
459
		// Do we need to write a cache file? Only if the controller does not have its
460
		// own _output() method and we are not dealing with a cache file, which we
461
		// can determine by the existence of the $CI object above
462
		if ($this->cache_expiration > 0 && isset($CI) && ! method_exists($CI, '_output'))
463
		{
464
			$this->_write_cache($output);
465
		}
466
467
		// --------------------------------------------------------------------
468
469
		// Parse out the elapsed time and memory usage,
470
		// then swap the pseudo-variables with the data
471
472
		$elapsed = $BM->elapsed_time('total_execution_time_start', 'total_execution_time_end');
473
474
		if ($this->parse_exec_vars === TRUE)
475
		{
476
			$memory	= round(memory_get_usage() / 1024 / 1024, 2).'MB';
477
			$output = str_replace(array('{elapsed_time}', '{memory_usage}'), array($elapsed, $memory), $output);
478
		}
479
480
		// --------------------------------------------------------------------
481
482
		// Is compression requested?
483
		if (isset($CI) // This means that we're not serving a cache file, if we were, it would already be compressed
484
			&& $this->_compress_output === TRUE
485
			&& isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
486
		{
487
			ob_start('ob_gzhandler');
488
		}
489
490
		// --------------------------------------------------------------------
491
492
		// Are there any server headers to send?
493
		if (count($this->headers) > 0)
494
		{
495
			foreach ($this->headers as $header)
0 ignored issues
show
Unused Code introduced by
This foreach statement is empty and can be removed.

This check looks for foreach loops that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

Consider removing the loop.

Loading history...
496
			{
497
//				@header($header[0], $header[1]);
498
			}
499
		}
500
501
		// --------------------------------------------------------------------
502
503
		// Does the $CI object exist?
504
		// If not we know we are dealing with a cache file so we'll
505
		// simply echo out the data and exit.
506
		if ( ! isset($CI))
507
		{
508
			if ($this->_compress_output === TRUE)
509
			{
510
				if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
511
				{
512
//					header('Content-Encoding: gzip');
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
513
//					header('Content-Length: '.self::strlen($output));
514
				}
515
				else
516
				{
517
					// User agent doesn't support gzip compression,
518
					// so we'll have to decompress our cache
519
					$output = gzinflate(self::substr($output, 10, -8));
520
				}
521
			}
522
523
			echo $output;
524
			log_message('info', 'Final output sent to browser');
525
			log_message('debug', 'Total execution time: '.$elapsed);
526
			return;
527
		}
528
529
		// --------------------------------------------------------------------
530
531
		// Do we need to generate profile data?
532
		// If so, load the Profile class and run it.
533
		if ($this->enable_profiler === TRUE)
534
		{
535
			$CI->load->library('profiler');
536
			if ( ! empty($this->_profiler_sections))
537
			{
538
				$CI->profiler->set_sections($this->_profiler_sections);
539
			}
540
541
			// If the output data contains closing </body> and </html> tags
542
			// we will remove them and add them back after we insert the profile data
543
			$output = preg_replace('|</body>.*?</html>|is', '', $output, -1, $count).$CI->profiler->run();
544
			if ($count > 0)
545
			{
546
				$output .= '</body></html>';
547
			}
548
		}
549
550
		// Does the controller contain a function named _output()?
551
		// If so send the output there.  Otherwise, echo it.
552
		if (method_exists($CI, '_output'))
553
		{
554
			$CI->_output($output);
555
		}
556
		else
557
		{
558
			echo $output; // Send it to the browser!
559
		}
560
561
		log_message('info', 'Final output sent to browser');
562
		log_message('debug', 'Total execution time: '.$elapsed);
563
	}
564
565
	// --------------------------------------------------------------------
566
567
	/**
568
	 * Write Cache
569
	 *
570
	 * @param	string	$output	Output data to cache
571
	 * @return	void
572
	 */
573
	public function _write_cache($output)
0 ignored issues
show
Coding Style introduced by
_write_cache uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
_write_cache uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
574
	{
575
		$CI =& get_instance();
576
		$path = $CI->config->item('cache_path');
577
		$cache_path = ($path === '') ? APPPATH.'cache/' : $path;
578
579
		if ( ! is_dir($cache_path) OR ! is_really_writable($cache_path))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
580
		{
581
			log_message('error', 'Unable to write cache file: '.$cache_path);
582
			return;
583
		}
584
585
		$uri = $CI->config->item('base_url')
586
			.$CI->config->item('index_page')
587
			.$CI->uri->uri_string();
588
589 View Code Duplication
		if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
590
		{
591
			if (is_array($cache_query_string))
592
			{
593
				$uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string)));
594
			}
595
			else
596
			{
597
				$uri .= '?'.$_SERVER['QUERY_STRING'];
598
			}
599
		}
600
601
		$cache_path .= md5($uri);
602
603
		if ( ! $fp = @fopen($cache_path, 'w+b'))
604
		{
605
			log_message('error', 'Unable to write cache file: '.$cache_path);
606
			return;
607
		}
608
609
		if ( ! flock($fp, LOCK_EX))
610
		{
611
			log_message('error', 'Unable to secure a file lock for file at: '.$cache_path);
612
			fclose($fp);
613
			return;
614
		}
615
616
		// If output compression is enabled, compress the cache
617
		// itself, so that we don't have to do that each time
618
		// we're serving it
619
		if ($this->_compress_output === TRUE)
620
		{
621
			$output = gzencode($output);
622
623
			if ($this->get_header('content-type') === NULL)
624
			{
625
				$this->set_content_type($this->mime_type);
626
			}
627
		}
628
629
		$expire = time() + ($this->cache_expiration * 60);
630
631
		// Put together our serialized info.
632
		$cache_info = serialize(array(
633
			'expire'	=> $expire,
634
			'headers'	=> $this->headers
635
		));
636
637
		$output = $cache_info.'ENDCI--->'.$output;
638
639
		for ($written = 0, $length = self::strlen($output); $written < $length; $written += $result)
640
		{
641
			if (($result = fwrite($fp, self::substr($output, $written))) === FALSE)
642
			{
643
				break;
644
			}
645
		}
646
647
		flock($fp, LOCK_UN);
648
		fclose($fp);
649
650
		if ( ! is_int($result))
0 ignored issues
show
Bug introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
651
		{
652
			@unlink($cache_path);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
653
			log_message('error', 'Unable to write the complete cache content at: '.$cache_path);
654
			return;
655
		}
656
657
		chmod($cache_path, 0640);
658
		log_message('debug', 'Cache file written: '.$cache_path);
659
660
		// Send HTTP cache-control headers to browser to match file cache settings.
661
		$this->set_cache_header($_SERVER['REQUEST_TIME'], $expire);
662
	}
663
664
	// --------------------------------------------------------------------
665
666
	/**
667
	 * Update/serve cached output
668
	 *
669
	 * @uses	CI_Config
670
	 * @uses	CI_URI
671
	 *
672
	 * @param	object	&$CFG	CI_Config class instance
673
	 * @param	object	&$URI	CI_URI class instance
674
	 * @return	bool	TRUE on success or FALSE on failure
675
	 */
676
	public function _display_cache(&$CFG, &$URI)
0 ignored issues
show
Coding Style introduced by
_display_cache uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
_display_cache uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
677
	{
678
		$cache_path = ($CFG->item('cache_path') === '') ? APPPATH.'cache/' : $CFG->item('cache_path');
679
680
		// Build the file path. The file name is an MD5 hash of the full URI
681
		$uri = $CFG->item('base_url').$CFG->item('index_page').$URI->uri_string;
682
683 View Code Duplication
		if (($cache_query_string = $CFG->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
684
		{
685
			if (is_array($cache_query_string))
686
			{
687
				$uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string)));
688
			}
689
			else
690
			{
691
				$uri .= '?'.$_SERVER['QUERY_STRING'];
692
			}
693
		}
694
695
		$filepath = $cache_path.md5($uri);
696
697
		if ( ! file_exists($filepath) OR ! $fp = @fopen($filepath, 'rb'))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
698
		{
699
			return FALSE;
700
		}
701
702
		flock($fp, LOCK_SH);
703
704
		$cache = (filesize($filepath) > 0) ? fread($fp, filesize($filepath)) : '';
705
706
		flock($fp, LOCK_UN);
707
		fclose($fp);
708
709
		// Look for embedded serialized file info.
710
		if ( ! preg_match('/^(.*)ENDCI--->/', $cache, $match))
711
		{
712
			return FALSE;
713
		}
714
715
		$cache_info = unserialize($match[1]);
716
		$expire = $cache_info['expire'];
717
718
		$last_modified = filemtime($filepath);
719
720
		// Has the file expired?
721
		if ($_SERVER['REQUEST_TIME'] >= $expire && is_really_writable($cache_path))
722
		{
723
			// If so we'll delete it.
724
			@unlink($filepath);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
725
			log_message('debug', 'Cache file has expired. File deleted.');
726
			return FALSE;
727
		}
728
729
		// Send the HTTP cache control headers
730
		$this->set_cache_header($last_modified, $expire);
731
732
		// Add headers from cache file.
733
		foreach ($cache_info['headers'] as $header)
734
		{
735
			$this->set_header($header[0], $header[1]);
736
		}
737
738
		// Display the cache
739
		$this->_display(self::substr($cache, self::strlen($match[0])));
740
		log_message('debug', 'Cache file is current. Sending it to browser.');
741
		return TRUE;
742
	}
743
744
	// --------------------------------------------------------------------
745
746
	/**
747
	 * Delete cache
748
	 *
749
	 * @param	string	$uri	URI string
750
	 * @return	bool
751
	 */
752
	public function delete_cache($uri = '')
0 ignored issues
show
Coding Style introduced by
delete_cache uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
delete_cache uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
753
	{
754
		$CI =& get_instance();
755
		$cache_path = $CI->config->item('cache_path');
756
		if ($cache_path === '')
757
		{
758
			$cache_path = APPPATH.'cache/';
759
		}
760
761
		if ( ! is_dir($cache_path))
762
		{
763
			log_message('error', 'Unable to find cache path: '.$cache_path);
764
			return FALSE;
765
		}
766
767
		if (empty($uri))
768
		{
769
			$uri = $CI->uri->uri_string();
770
771 View Code Duplication
			if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
772
			{
773
				if (is_array($cache_query_string))
774
				{
775
					$uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string)));
776
				}
777
				else
778
				{
779
					$uri .= '?'.$_SERVER['QUERY_STRING'];
780
				}
781
			}
782
		}
783
784
		$cache_path .= md5($CI->config->item('base_url').$CI->config->item('index_page').ltrim($uri, '/'));
785
786
		if ( ! @unlink($cache_path))
787
		{
788
			log_message('error', 'Unable to delete cache file for '.$uri);
789
			return FALSE;
790
		}
791
792
		return TRUE;
793
	}
794
795
	// --------------------------------------------------------------------
796
797
	/**
798
	 * Set Cache Header
799
	 *
800
	 * Set the HTTP headers to match the server-side file cache settings
801
	 * in order to reduce bandwidth.
802
	 *
803
	 * @param	int	$last_modified	Timestamp of when the page was last modified
804
	 * @param	int	$expiration	Timestamp of when should the requested page expire from cache
805
	 * @return	void
806
	 *
807
	 * modified by ci-phpunit-test
808
	 */
809
	public function set_cache_header($last_modified, $expiration)
0 ignored issues
show
Coding Style introduced by
set_cache_header uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
810
	{
811
		$max_age = $expiration - $_SERVER['REQUEST_TIME'];
0 ignored issues
show
Unused Code introduced by
$max_age is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
812
813
		if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $last_modified <= strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']))
814
		{
815
			$this->set_status_header(304);
816
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method set_cache_header() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
817
		}
818
819
//		header('Pragma: public');
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
820
//		header('Cache-Control: max-age='.$max_age.', public');
821
//		header('Expires: '.gmdate('D, d M Y H:i:s', $expiration).' GMT');
822
//		header('Last-modified: '.gmdate('D, d M Y H:i:s', $last_modified).' GMT');
823
	}
824
825
	// --------------------------------------------------------------------
826
827
	/**
828
	 * Byte-safe strlen()
829
	 *
830
	 * @param	string	$str
831
	 * @return	int
832
	 */
833
	protected static function strlen($str)
834
	{
835
		return (self::$func_overload)
836
			? mb_strlen($str, '8bit')
837
			: strlen($str);
838
	}
839
840
	// --------------------------------------------------------------------
841
842
	/**
843
	 * Byte-safe substr()
844
	 *
845
	 * @param	string	$str
846
	 * @param	int	$start
847
	 * @param	int	$length
848
	 * @return	string
849
	 */
850
	protected static function substr($str, $start, $length = NULL)
851
	{
852
		if (self::$func_overload)
853
		{
854
			// mb_substr($str, $start, null, '8bit') returns an empty
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
855
			// string on PHP 5.3
856
			isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start);
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
857
			return mb_substr($str, $start, $length, '8bit');
858
		}
859
860
		return isset($length)
861
			? substr($str, $start, $length)
862
			: substr($str, $start);
863
	}
864
}
865