blob: 3cda062aa2093f9a73a09280a195a96660ae7bec [file] [log] [blame]
Michael Dodge362b8002013-01-04 23:18:39 -07001<?php
2/**
3 * CodeIgniter
4 *
Andrey Andreevfe9309d2015-01-09 17:48:58 +02005 * An open source application development framework for PHP
Michael Dodge362b8002013-01-04 23:18:39 -07006 *
Andrey Andreevbdb96ca2014-10-28 00:13:31 +02007 * This content is released under the MIT License (MIT)
Michael Dodge362b8002013-01-04 23:18:39 -07008 *
Andrey Andreevcce6bd12018-01-09 11:32:02 +02009 * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
Michael Dodge362b8002013-01-04 23:18:39 -070010 *
Andrey Andreevbdb96ca2014-10-28 00:13:31 +020011 * 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:
Michael Dodge362b8002013-01-04 23:18:39 -070017 *
Andrey Andreevbdb96ca2014-10-28 00:13:31 +020018 * 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
Andrey Andreev1924e872016-01-11 12:55:34 +020031 * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
Andrey Andreevcce6bd12018-01-09 11:32:02 +020032 * @copyright Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
Andrey Andreevbdb96ca2014-10-28 00:13:31 +020033 * @license http://opensource.org/licenses/MIT MIT License
Andrey Andreevbd202c92016-01-11 12:50:18 +020034 * @link https://codeigniter.com
Andrey Andreevbdb96ca2014-10-28 00:13:31 +020035 * @since Version 1.0.0
Michael Dodge362b8002013-01-04 23:18:39 -070036 * @filesource
37 */
38defined('BASEPATH') OR exit('No direct script access allowed');
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
Andrey Andreevbd202c92016-01-11 12:50:18 +020049 * @link https://codeigniter.com/user_guide/libraries/output.html
Michael Dodge362b8002013-01-04 23:18:39 -070050 */
51class 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 */
Andrey Andreev155ee722014-01-10 15:50:54 +020072 public $headers = array();
Michael Dodge362b8002013-01-04 23:18:39 -070073
74 /**
75 * List of mime types
76 *
77 * @var array
78 */
Andrey Andreev155ee722014-01-10 15:50:54 +020079 public $mimes = array();
Michael Dodge362b8002013-01-04 23:18:39 -070080
81 /**
82 * Mime-type for the current page
83 *
84 * @var string
85 */
Andrey Andreev155ee722014-01-10 15:50:54 +020086 protected $mime_type = 'text/html';
Michael Dodge362b8002013-01-04 23:18:39 -070087
88 /**
89 * Enable Profiler flag
90 *
91 * @var bool
92 */
93 public $enable_profiler = FALSE;
94
95 /**
Andrey Andreev155ee722014-01-10 15:50:54 +020096 * php.ini zlib.output_compression flag
Michael Dodge362b8002013-01-04 23:18:39 -070097 *
98 * @var bool
99 */
Andrey Andreev155ee722014-01-10 15:50:54 +0200100 protected $_zlib_oc = FALSE;
101
102 /**
103 * CI output compression flag
104 *
105 * @var bool
106 */
107 protected $_compress_output = FALSE;
Michael Dodge362b8002013-01-04 23:18:39 -0700108
109 /**
110 * List of profiler sections
111 *
112 * @var array
113 */
Eric Roberts3e6b5822013-01-17 18:30:25 -0600114 protected $_profiler_sections = array();
Michael Dodge362b8002013-01-04 23:18:39 -0700115
116 /**
117 * Parse markers flag
118 *
119 * Whether or not to parse variables like {elapsed_time} and {memory_usage}.
120 *
121 * @var bool
122 */
Andrey Andreev155ee722014-01-10 15:50:54 +0200123 public $parse_exec_vars = TRUE;
Michael Dodge362b8002013-01-04 23:18:39 -0700124
125 /**
Andrey Andreevc0c74d52017-01-19 15:26:35 +0200126 * mbstring.func_overload flag
Andrey Andreev4e2cdec2016-10-28 14:19:08 +0300127 *
128 * @var bool
129 */
Andrey Andreevc0c74d52017-01-19 15:26:35 +0200130 protected static $func_overload;
Andrey Andreev4e2cdec2016-10-28 14:19:08 +0300131
132 /**
Michael Dodge362b8002013-01-04 23:18:39 -0700133 * Class constructor
134 *
135 * Determines whether zLib output compression will be used.
136 *
137 * @return void
138 */
139 public function __construct()
140 {
Andrey Andreevf6274742014-02-20 18:05:58 +0200141 $this->_zlib_oc = (bool) ini_get('zlib.output_compression');
Andrey Andreev155ee722014-01-10 15:50:54 +0200142 $this->_compress_output = (
143 $this->_zlib_oc === FALSE
Andrey Andreev9916bfc2014-01-10 16:21:07 +0200144 && config_item('compress_output') === TRUE
Andrey Andreev155ee722014-01-10 15:50:54 +0200145 && extension_loaded('zlib')
146 );
Michael Dodge362b8002013-01-04 23:18:39 -0700147
Andrey Andreevc0c74d52017-01-19 15:26:35 +0200148 isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload'));
Andrey Andreev4e2cdec2016-10-28 14:19:08 +0300149
Michael Dodge362b8002013-01-04 23:18:39 -0700150 // Get mime types for later
151 $this->mimes =& get_mimes();
152
Andrey Andreev90726b82015-01-20 12:39:22 +0200153 log_message('info', 'Output Class Initialized');
Michael Dodge362b8002013-01-04 23:18:39 -0700154 }
155
156 // --------------------------------------------------------------------
157
158 /**
159 * Get Output
160 *
161 * Returns the current output string.
162 *
163 * @return string
164 */
165 public function get_output()
166 {
167 return $this->final_output;
168 }
169
170 // --------------------------------------------------------------------
171
172 /**
173 * Set Output
174 *
175 * Sets the output string.
176 *
177 * @param string $output Output data
178 * @return CI_Output
179 */
180 public function set_output($output)
181 {
182 $this->final_output = $output;
183 return $this;
184 }
185
186 // --------------------------------------------------------------------
187
188 /**
189 * Append Output
190 *
191 * Appends data onto the output string.
192 *
193 * @param string $output Data to append
194 * @return CI_Output
195 */
196 public function append_output($output)
197 {
Andrey Andreev2ab4ffb2014-02-24 16:15:09 +0200198 $this->final_output .= $output;
Michael Dodge362b8002013-01-04 23:18:39 -0700199 return $this;
200 }
201
202 // --------------------------------------------------------------------
203
204 /**
205 * Set Header
206 *
207 * Lets you set a server header which will be sent with the final output.
208 *
209 * Note: If a file is cached, headers will not be sent.
210 * @todo We need to figure out how to permit headers to be cached.
211 *
212 * @param string $header Header
213 * @param bool $replace Whether to replace the old header value, if already set
214 * @return CI_Output
215 */
216 public function set_header($header, $replace = TRUE)
217 {
218 // If zlib.output_compression is enabled it will compress the output,
219 // but it will not modify the content-length header to compensate for
220 // the reduction, causing the browser to hang waiting for more data.
221 // We'll just skip content-length in those cases.
222 if ($this->_zlib_oc && strncasecmp($header, 'content-length', 14) === 0)
223 {
224 return $this;
225 }
226
227 $this->headers[] = array($header, $replace);
228 return $this;
229 }
230
231 // --------------------------------------------------------------------
232
233 /**
234 * Set Content-Type Header
235 *
236 * @param string $mime_type Extension of the file we're outputting
237 * @param string $charset Character set (default: NULL)
238 * @return CI_Output
239 */
240 public function set_content_type($mime_type, $charset = NULL)
241 {
242 if (strpos($mime_type, '/') === FALSE)
243 {
244 $extension = ltrim($mime_type, '.');
245
246 // Is this extension supported?
247 if (isset($this->mimes[$extension]))
248 {
249 $mime_type =& $this->mimes[$extension];
250
251 if (is_array($mime_type))
252 {
253 $mime_type = current($mime_type);
254 }
255 }
256 }
257
258 $this->mime_type = $mime_type;
259
260 if (empty($charset))
261 {
262 $charset = config_item('charset');
263 }
264
265 $header = 'Content-Type: '.$mime_type
vlakoffd5ce5082014-04-25 10:13:04 +0200266 .(empty($charset) ? '' : '; charset='.$charset);
Michael Dodge362b8002013-01-04 23:18:39 -0700267
268 $this->headers[] = array($header, TRUE);
269 return $this;
270 }
271
272 // --------------------------------------------------------------------
273
274 /**
275 * Get Current Content-Type Header
276 *
277 * @return string 'text/html', if not already set
278 */
279 public function get_content_type()
280 {
281 for ($i = 0, $c = count($this->headers); $i < $c; $i++)
282 {
283 if (sscanf($this->headers[$i][0], 'Content-Type: %[^;]', $content_type) === 1)
284 {
285 return $content_type;
286 }
287 }
288
289 return 'text/html';
290 }
291
292 // --------------------------------------------------------------------
293
294 /**
295 * Get Header
296 *
Andrey Andreeve13fa9f2016-05-20 17:30:07 +0300297 * @param string $header
Michael Dodge362b8002013-01-04 23:18:39 -0700298 * @return string
299 */
300 public function get_header($header)
301 {
302 // Combine headers already sent with our batched headers
303 $headers = array_merge(
304 // We only need [x][0] from our multi-dimensional array
305 array_map('array_shift', $this->headers),
306 headers_list()
307 );
308
309 if (empty($headers) OR empty($header))
310 {
311 return NULL;
312 }
313
Andrey Andreeve3779102016-12-01 13:48:58 +0200314 // Count backwards, in order to get the last matching header
315 for ($c = count($headers) - 1; $c > -1; $c--)
Michael Dodge362b8002013-01-04 23:18:39 -0700316 {
Andrey Andreeve3779102016-12-01 13:48:58 +0200317 if (strncasecmp($header, $headers[$c], $l = self::strlen($header)) === 0)
Michael Dodge362b8002013-01-04 23:18:39 -0700318 {
Andrey Andreeve3779102016-12-01 13:48:58 +0200319 return trim(self::substr($headers[$c], $l+1));
Michael Dodge362b8002013-01-04 23:18:39 -0700320 }
321 }
322
323 return NULL;
324 }
325
326 // --------------------------------------------------------------------
327
328 /**
329 * Set HTTP Status Header
330 *
331 * As of version 1.7.2, this is an alias for common function
332 * set_status_header().
333 *
334 * @param int $code Status code (default: 200)
335 * @param string $text Optional message
336 * @return CI_Output
337 */
338 public function set_status_header($code = 200, $text = '')
339 {
340 set_status_header($code, $text);
341 return $this;
342 }
343
344 // --------------------------------------------------------------------
345
346 /**
347 * Enable/disable Profiler
348 *
349 * @param bool $val TRUE to enable or FALSE to disable
350 * @return CI_Output
351 */
352 public function enable_profiler($val = TRUE)
353 {
354 $this->enable_profiler = is_bool($val) ? $val : TRUE;
355 return $this;
356 }
357
358 // --------------------------------------------------------------------
359
360 /**
361 * Set Profiler Sections
362 *
363 * Allows override of default/config settings for
364 * Profiler section display.
365 *
366 * @param array $sections Profiler sections
367 * @return CI_Output
368 */
369 public function set_profiler_sections($sections)
370 {
371 if (isset($sections['query_toggle_count']))
372 {
373 $this->_profiler_sections['query_toggle_count'] = (int) $sections['query_toggle_count'];
374 unset($sections['query_toggle_count']);
375 }
376
377 foreach ($sections as $section => $enable)
378 {
379 $this->_profiler_sections[$section] = ($enable !== FALSE);
380 }
381
382 return $this;
383 }
384
385 // --------------------------------------------------------------------
386
387 /**
388 * Set Cache
389 *
Andrey Andreev22df06b2016-01-20 12:10:08 +0200390 * @param int $time Cache expiration time in minutes
Michael Dodge362b8002013-01-04 23:18:39 -0700391 * @return CI_Output
392 */
393 public function cache($time)
394 {
395 $this->cache_expiration = is_numeric($time) ? $time : 0;
396 return $this;
397 }
398
399 // --------------------------------------------------------------------
400
401 /**
402 * Display Output
403 *
natepizzle21432ab2015-02-03 16:25:42 -0600404 * Processes and sends finalized output data to the browser along
Michael Dodge362b8002013-01-04 23:18:39 -0700405 * with any server headers and profile data. It also stops benchmark
406 * timers so the page rendering speed and memory usage can be shown.
407 *
408 * Note: All "view" data is automatically put into $this->final_output
409 * by controller class.
410 *
411 * @uses CI_Output::$final_output
412 * @param string $output Output data override
413 * @return void
414 */
415 public function _display($output = '')
416 {
Andrey Andreevc26b9eb2014-02-24 11:31:36 +0200417 // Note: We use load_class() because we can't use $CI =& get_instance()
Michael Dodge362b8002013-01-04 23:18:39 -0700418 // since this function is sometimes called by the caching mechanism,
419 // which happens before the CI super object is available.
Andrey Andreevc26b9eb2014-02-24 11:31:36 +0200420 $BM =& load_class('Benchmark', 'core');
421 $CFG =& load_class('Config', 'core');
Michael Dodge362b8002013-01-04 23:18:39 -0700422
423 // Grab the super object if we can.
Andrey Andreev49e68de2013-02-21 16:30:55 +0200424 if (class_exists('CI_Controller', FALSE))
Michael Dodge362b8002013-01-04 23:18:39 -0700425 {
426 $CI =& get_instance();
427 }
428
429 // --------------------------------------------------------------------
430
431 // Set the output data
432 if ($output === '')
433 {
434 $output =& $this->final_output;
435 }
436
437 // --------------------------------------------------------------------
438
Michael Dodge362b8002013-01-04 23:18:39 -0700439 // Do we need to write a cache file? Only if the controller does not have its
440 // own _output() method and we are not dealing with a cache file, which we
441 // can determine by the existence of the $CI object above
442 if ($this->cache_expiration > 0 && isset($CI) && ! method_exists($CI, '_output'))
443 {
444 $this->_write_cache($output);
445 }
446
447 // --------------------------------------------------------------------
448
449 // Parse out the elapsed time and memory usage,
450 // then swap the pseudo-variables with the data
451
452 $elapsed = $BM->elapsed_time('total_execution_time_start', 'total_execution_time_end');
453
454 if ($this->parse_exec_vars === TRUE)
455 {
456 $memory = round(memory_get_usage() / 1024 / 1024, 2).'MB';
Michael Dodge362b8002013-01-04 23:18:39 -0700457 $output = str_replace(array('{elapsed_time}', '{memory_usage}'), array($elapsed, $memory), $output);
458 }
459
460 // --------------------------------------------------------------------
461
462 // Is compression requested?
Andrey Andreev155ee722014-01-10 15:50:54 +0200463 if (isset($CI) // This means that we're not serving a cache file, if we were, it would already be compressed
464 && $this->_compress_output === TRUE
Michael Dodge362b8002013-01-04 23:18:39 -0700465 && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
466 {
467 ob_start('ob_gzhandler');
468 }
469
470 // --------------------------------------------------------------------
471
472 // Are there any server headers to send?
473 if (count($this->headers) > 0)
474 {
475 foreach ($this->headers as $header)
476 {
477 @header($header[0], $header[1]);
478 }
479 }
480
481 // --------------------------------------------------------------------
482
483 // Does the $CI object exist?
484 // If not we know we are dealing with a cache file so we'll
485 // simply echo out the data and exit.
486 if ( ! isset($CI))
487 {
Andrey Andreev155ee722014-01-10 15:50:54 +0200488 if ($this->_compress_output === TRUE)
489 {
490 if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
491 {
492 header('Content-Encoding: gzip');
Andrey Andreev4e2cdec2016-10-28 14:19:08 +0300493 header('Content-Length: '.self::strlen($output));
Andrey Andreev155ee722014-01-10 15:50:54 +0200494 }
495 else
496 {
497 // User agent doesn't support gzip compression,
498 // so we'll have to decompress our cache
Andrey Andreev4e2cdec2016-10-28 14:19:08 +0300499 $output = gzinflate(self::substr($output, 10, -8));
Andrey Andreev155ee722014-01-10 15:50:54 +0200500 }
501 }
502
Michael Dodge362b8002013-01-04 23:18:39 -0700503 echo $output;
Andrey Andreev90726b82015-01-20 12:39:22 +0200504 log_message('info', 'Final output sent to browser');
Michael Dodge362b8002013-01-04 23:18:39 -0700505 log_message('debug', 'Total execution time: '.$elapsed);
506 return;
507 }
508
509 // --------------------------------------------------------------------
510
511 // Do we need to generate profile data?
512 // If so, load the Profile class and run it.
513 if ($this->enable_profiler === TRUE)
514 {
515 $CI->load->library('profiler');
516 if ( ! empty($this->_profiler_sections))
517 {
518 $CI->profiler->set_sections($this->_profiler_sections);
519 }
520
521 // If the output data contains closing </body> and </html> tags
522 // we will remove them and add them back after we insert the profile data
523 $output = preg_replace('|</body>.*?</html>|is', '', $output, -1, $count).$CI->profiler->run();
524 if ($count > 0)
525 {
526 $output .= '</body></html>';
527 }
528 }
529
530 // Does the controller contain a function named _output()?
531 // If so send the output there. Otherwise, echo it.
532 if (method_exists($CI, '_output'))
533 {
534 $CI->_output($output);
535 }
536 else
537 {
538 echo $output; // Send it to the browser!
539 }
540
Andrey Andreev90726b82015-01-20 12:39:22 +0200541 log_message('info', 'Final output sent to browser');
Michael Dodge362b8002013-01-04 23:18:39 -0700542 log_message('debug', 'Total execution time: '.$elapsed);
543 }
544
545 // --------------------------------------------------------------------
546
547 /**
548 * Write Cache
549 *
550 * @param string $output Output data to cache
551 * @return void
552 */
553 public function _write_cache($output)
554 {
555 $CI =& get_instance();
556 $path = $CI->config->item('cache_path');
557 $cache_path = ($path === '') ? APPPATH.'cache/' : $path;
558
559 if ( ! is_dir($cache_path) OR ! is_really_writable($cache_path))
560 {
561 log_message('error', 'Unable to write cache file: '.$cache_path);
562 return;
563 }
564
Andrey Andreev155ee722014-01-10 15:50:54 +0200565 $uri = $CI->config->item('base_url')
566 .$CI->config->item('index_page')
567 .$CI->uri->uri_string();
Michael Dodge362b8002013-01-04 23:18:39 -0700568
Andrey Andreev298e0052015-07-15 17:17:18 +0300569 if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
Andrey Andreeva704aa72014-12-04 12:37:07 +0200570 {
w0dendc29c6d2015-05-11 18:58:20 +0300571 if (is_array($cache_query_string))
572 {
573 $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string)));
574 }
575 else
576 {
577 $uri .= '?'.$_SERVER['QUERY_STRING'];
578 }
Andrey Andreeva704aa72014-12-04 12:37:07 +0200579 }
Stefano Mazzegaac41ca62014-12-03 11:55:47 +0100580
Michael Dodge362b8002013-01-04 23:18:39 -0700581 $cache_path .= md5($uri);
582
Andrey Andreev7cf682a2014-03-13 14:55:45 +0200583 if ( ! $fp = @fopen($cache_path, 'w+b'))
Michael Dodge362b8002013-01-04 23:18:39 -0700584 {
585 log_message('error', 'Unable to write cache file: '.$cache_path);
586 return;
587 }
588
Andrey Andreevf8fba7b2017-04-18 12:03:15 +0300589 if ( ! flock($fp, LOCK_EX))
Michael Dodge362b8002013-01-04 23:18:39 -0700590 {
591 log_message('error', 'Unable to secure a file lock for file at: '.$cache_path);
Andrey Andreevf8fba7b2017-04-18 12:03:15 +0300592 fclose($fp);
Michael Dodge362b8002013-01-04 23:18:39 -0700593 return;
594 }
Andrey Andreev155ee722014-01-10 15:50:54 +0200595
Andrey Andreevf8fba7b2017-04-18 12:03:15 +0300596 // If output compression is enabled, compress the cache
597 // itself, so that we don't have to do that each time
598 // we're serving it
599 if ($this->_compress_output === TRUE)
600 {
601 $output = gzencode($output);
602
603 if ($this->get_header('content-type') === NULL)
604 {
605 $this->set_content_type($this->mime_type);
606 }
607 }
608
609 $expire = time() + ($this->cache_expiration * 60);
610
611 // Put together our serialized info.
612 $cache_info = serialize(array(
613 'expire' => $expire,
614 'headers' => $this->headers
615 ));
616
617 $output = $cache_info.'ENDCI--->'.$output;
618
619 for ($written = 0, $length = self::strlen($output); $written < $length; $written += $result)
620 {
621 if (($result = fwrite($fp, self::substr($output, $written))) === FALSE)
622 {
623 break;
624 }
625 }
626
627 flock($fp, LOCK_UN);
Michael Dodge362b8002013-01-04 23:18:39 -0700628 fclose($fp);
Michael Dodge362b8002013-01-04 23:18:39 -0700629
Andrey Andreevf8fba7b2017-04-18 12:03:15 +0300630 if ( ! is_int($result))
Andrey Andreevd8b1ad32014-01-15 17:42:52 +0200631 {
632 @unlink($cache_path);
633 log_message('error', 'Unable to write the complete cache content at: '.$cache_path);
Andrey Andreevf8fba7b2017-04-18 12:03:15 +0300634 return;
Andrey Andreevd8b1ad32014-01-15 17:42:52 +0200635 }
Andrey Andreevf8fba7b2017-04-18 12:03:15 +0300636
637 chmod($cache_path, 0640);
638 log_message('debug', 'Cache file written: '.$cache_path);
639
640 // Send HTTP cache-control headers to browser to match file cache settings.
641 $this->set_cache_header($_SERVER['REQUEST_TIME'], $expire);
Michael Dodge362b8002013-01-04 23:18:39 -0700642 }
643
644 // --------------------------------------------------------------------
645
646 /**
647 * Update/serve cached output
648 *
649 * @uses CI_Config
650 * @uses CI_URI
651 *
652 * @param object &$CFG CI_Config class instance
653 * @param object &$URI CI_URI class instance
654 * @return bool TRUE on success or FALSE on failure
655 */
656 public function _display_cache(&$CFG, &$URI)
657 {
658 $cache_path = ($CFG->item('cache_path') === '') ? APPPATH.'cache/' : $CFG->item('cache_path');
659
660 // Build the file path. The file name is an MD5 hash of the full URI
Andrey Andreev33572252014-12-03 20:19:38 +0200661 $uri = $CFG->item('base_url').$CFG->item('index_page').$URI->uri_string;
Andrey Andreeva704aa72014-12-04 12:37:07 +0200662
Andrey Andreev298e0052015-07-15 17:17:18 +0300663 if (($cache_query_string = $CFG->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
Andrey Andreeva704aa72014-12-04 12:37:07 +0200664 {
w0dendc29c6d2015-05-11 18:58:20 +0300665 if (is_array($cache_query_string))
666 {
667 $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string)));
668 }
669 else
670 {
671 $uri .= '?'.$_SERVER['QUERY_STRING'];
672 }
Andrey Andreeva704aa72014-12-04 12:37:07 +0200673 }
Stefano Mazzegaac41ca62014-12-03 11:55:47 +0100674
Michael Dodge362b8002013-01-04 23:18:39 -0700675 $filepath = $cache_path.md5($uri);
676
Andrey Andreev7cf682a2014-03-13 14:55:45 +0200677 if ( ! file_exists($filepath) OR ! $fp = @fopen($filepath, 'rb'))
Michael Dodge362b8002013-01-04 23:18:39 -0700678 {
679 return FALSE;
680 }
681
682 flock($fp, LOCK_SH);
683
684 $cache = (filesize($filepath) > 0) ? fread($fp, filesize($filepath)) : '';
685
686 flock($fp, LOCK_UN);
687 fclose($fp);
688
Eric Robertsc90e67e2013-01-11 21:20:54 -0600689 // Look for embedded serialized file info.
690 if ( ! preg_match('/^(.*)ENDCI--->/', $cache, $match))
Michael Dodge362b8002013-01-04 23:18:39 -0700691 {
692 return FALSE;
693 }
Purwandi5dc6d512013-01-19 17:43:08 +0700694
Eric Robertsc90e67e2013-01-11 21:20:54 -0600695 $cache_info = unserialize($match[1]);
696 $expire = $cache_info['expire'];
Michael Dodge362b8002013-01-04 23:18:39 -0700697
Ivan Tcholakovfd1bc222015-04-23 23:21:08 +0300698 $last_modified = filemtime($filepath);
Michael Dodge362b8002013-01-04 23:18:39 -0700699
700 // Has the file expired?
701 if ($_SERVER['REQUEST_TIME'] >= $expire && is_really_writable($cache_path))
702 {
703 // If so we'll delete it.
704 @unlink($filepath);
705 log_message('debug', 'Cache file has expired. File deleted.');
706 return FALSE;
707 }
Andrey Andreevf8fba7b2017-04-18 12:03:15 +0300708
709 // Send the HTTP cache control headers
710 $this->set_cache_header($last_modified, $expire);
Purwandi5dc6d512013-01-19 17:43:08 +0700711
Eric Robertsc90e67e2013-01-11 21:20:54 -0600712 // Add headers from cache file.
713 foreach ($cache_info['headers'] as $header)
714 {
715 $this->set_header($header[0], $header[1]);
716 }
Michael Dodge362b8002013-01-04 23:18:39 -0700717
718 // Display the cache
Andrey Andreev4e2cdec2016-10-28 14:19:08 +0300719 $this->_display(self::substr($cache, self::strlen($match[0])));
Michael Dodge362b8002013-01-04 23:18:39 -0700720 log_message('debug', 'Cache file is current. Sending it to browser.');
721 return TRUE;
722 }
723
724 // --------------------------------------------------------------------
725
726 /**
727 * Delete cache
728 *
729 * @param string $uri URI string
730 * @return bool
731 */
732 public function delete_cache($uri = '')
733 {
734 $CI =& get_instance();
735 $cache_path = $CI->config->item('cache_path');
736 if ($cache_path === '')
737 {
738 $cache_path = APPPATH.'cache/';
739 }
740
741 if ( ! is_dir($cache_path))
742 {
743 log_message('error', 'Unable to find cache path: '.$cache_path);
744 return FALSE;
745 }
746
747 if (empty($uri))
748 {
749 $uri = $CI->uri->uri_string();
Andrey Andreeva704aa72014-12-04 12:37:07 +0200750
Andrey Andreev298e0052015-07-15 17:17:18 +0300751 if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
Andrey Andreeva704aa72014-12-04 12:37:07 +0200752 {
w0dendc29c6d2015-05-11 18:58:20 +0300753 if (is_array($cache_query_string))
754 {
755 $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string)));
756 }
757 else
758 {
759 $uri .= '?'.$_SERVER['QUERY_STRING'];
760 }
Andrey Andreeva704aa72014-12-04 12:37:07 +0200761 }
Michael Dodge362b8002013-01-04 23:18:39 -0700762 }
763
w0den0b978dd2015-05-02 17:53:33 +0300764 $cache_path .= md5($CI->config->item('base_url').$CI->config->item('index_page').ltrim($uri, '/'));
Michael Dodge362b8002013-01-04 23:18:39 -0700765
766 if ( ! @unlink($cache_path))
767 {
768 log_message('error', 'Unable to delete cache file for '.$uri);
769 return FALSE;
770 }
771
772 return TRUE;
773 }
774
775 // --------------------------------------------------------------------
776
777 /**
778 * Set Cache Header
779 *
780 * Set the HTTP headers to match the server-side file cache settings
781 * in order to reduce bandwidth.
782 *
783 * @param int $last_modified Timestamp of when the page was last modified
784 * @param int $expiration Timestamp of when should the requested page expire from cache
785 * @return void
786 */
787 public function set_cache_header($last_modified, $expiration)
788 {
789 $max_age = $expiration - $_SERVER['REQUEST_TIME'];
790
791 if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $last_modified <= strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']))
792 {
793 $this->set_status_header(304);
794 exit;
795 }
Andrey Andreevf8fba7b2017-04-18 12:03:15 +0300796
797 header('Pragma: public');
798 header('Cache-Control: max-age='.$max_age.', public');
799 header('Expires: '.gmdate('D, d M Y H:i:s', $expiration).' GMT');
800 header('Last-modified: '.gmdate('D, d M Y H:i:s', $last_modified).' GMT');
Michael Dodge362b8002013-01-04 23:18:39 -0700801 }
802
Andrey Andreev4e2cdec2016-10-28 14:19:08 +0300803 // --------------------------------------------------------------------
804
805 /**
806 * Byte-safe strlen()
807 *
808 * @param string $str
809 * @return int
810 */
811 protected static function strlen($str)
812 {
Andrey Andreevc0c74d52017-01-19 15:26:35 +0200813 return (self::$func_overload)
Andrey Andreev4e2cdec2016-10-28 14:19:08 +0300814 ? mb_strlen($str, '8bit')
815 : strlen($str);
816 }
817
818 // --------------------------------------------------------------------
819
820 /**
821 * Byte-safe substr()
822 *
823 * @param string $str
824 * @param int $start
825 * @param int $length
826 * @return string
827 */
828 protected static function substr($str, $start, $length = NULL)
829 {
Andrey Andreevc0c74d52017-01-19 15:26:35 +0200830 if (self::$func_overload)
Andrey Andreev4e2cdec2016-10-28 14:19:08 +0300831 {
832 // mb_substr($str, $start, null, '8bit') returns an empty
833 // string on PHP 5.3
834 isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start);
835 return mb_substr($str, $start, $length, '8bit');
836 }
837
838 return isset($length)
839 ? substr($str, $start, $length)
840 : substr($str, $start);
841 }
Michael Dodge362b8002013-01-04 23:18:39 -0700842}