Code Duplication    Length = 814-814 lines in 4 locations

application/tests/_ci_phpunit_test/replacing/core/old/3.1.7-Output.php 1 location

@@ 51-864 (lines=814) @@
48
 * @author		EllisLab Dev Team
49
 * @link		https://codeigniter.com/user_guide/libraries/output.html
50
 */
51
class CI_Output {
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
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'));
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))
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;
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 = '')
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)
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)
511
				{
512
//					header('Content-Encoding: gzip');
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)
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))
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
		if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
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))
651
		{
652
			@unlink($cache_path);
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)
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
		if (($cache_query_string = $CFG->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
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'))
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);
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 = '')
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
			if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
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)
810
	{
811
		$max_age = $expiration - $_SERVER['REQUEST_TIME'];
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;
817
		}
818
819
//		header('Pragma: public');
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
855
			// string on PHP 5.3
856
			isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start);
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

application/tests/_ci_phpunit_test/replacing/core/old/3.1.8-Output.php 1 location

@@ 51-864 (lines=814) @@
48
 * @author		EllisLab Dev Team
49
 * @link		https://codeigniter.com/user_guide/libraries/output.html
50
 */
51
class CI_Output {
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
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'));
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))
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;
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 = '')
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)
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)
511
				{
512
//					header('Content-Encoding: gzip');
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)
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))
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
		if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
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))
651
		{
652
			@unlink($cache_path);
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)
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
		if (($cache_query_string = $CFG->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
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'))
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);
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 = '')
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
			if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
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)
810
	{
811
		$max_age = $expiration - $_SERVER['REQUEST_TIME'];
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;
817
		}
818
819
//		header('Pragma: public');
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
855
			// string on PHP 5.3
856
			isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start);
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

application/tests/_ci_phpunit_test/replacing/core/old/3.1.9-Output.php 1 location

@@ 51-864 (lines=814) @@
48
 * @author		EllisLab Dev Team
49
 * @link		https://codeigniter.com/user_guide/libraries/output.html
50
 */
51
class CI_Output {
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
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'));
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))
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;
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 = '')
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)
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)
511
				{
512
//					header('Content-Encoding: gzip');
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)
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))
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
		if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
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))
651
		{
652
			@unlink($cache_path);
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)
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
		if (($cache_query_string = $CFG->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
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'))
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);
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 = '')
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
			if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
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)
810
	{
811
		$max_age = $expiration - $_SERVER['REQUEST_TIME'];
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;
817
		}
818
819
//		header('Pragma: public');
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
855
			// string on PHP 5.3
856
			isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start);
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

application/tests/_ci_phpunit_test/replacing/core/Output.php 1 location

@@ 51-864 (lines=814) @@
48
 * @author		EllisLab Dev Team
49
 * @link		https://codeigniter.com/user_guide/libraries/output.html
50
 */
51
class CI_Output {
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
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'));
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))
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;
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 = '')
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)
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)
511
				{
512
//					header('Content-Encoding: gzip');
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)
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))
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
		if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
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))
651
		{
652
			@unlink($cache_path);
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)
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
		if (($cache_query_string = $CFG->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
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'))
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);
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 = '')
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
			if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
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)
810
	{
811
		$max_age = $expiration - $_SERVER['REQUEST_TIME'];
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;
817
		}
818
819
//		header('Pragma: public');
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
855
			// string on PHP 5.3
856
			isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start);
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