blob: cf6510ff16a8b061e9427552525e062e3ef5d606 [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 Andreev125ef472016-01-11 12:33:00 +02009 * Copyright (c) 2014 - 2016, 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 Andreev125ef472016-01-11 12:33:00 +020032 * @copyright Copyright (c) 2014 - 2016, 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 Andreev4e2cdec2016-10-28 14:19:08 +0300126 * mbstring.func_override flag
127 *
128 * @var bool
129 */
130 protected static $func_override;
131
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 Andreev4e2cdec2016-10-28 14:19:08 +0300148 isset(self::$func_override) OR self::$func_override = (extension_loaded('mbstring') && ini_get('mbstring.func_override'));
149
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
314 for ($i = 0, $c = count($headers); $i < $c; $i++)
315 {
Andrey Andreev4e2cdec2016-10-28 14:19:08 +0300316 if (strncasecmp($header, $headers[$i], $l = self::strlen($header)) === 0)
Michael Dodge362b8002013-01-04 23:18:39 -0700317 {
Andrey Andreev4e2cdec2016-10-28 14:19:08 +0300318 return trim(self::substr($headers[$i], $l+1));
Michael Dodge362b8002013-01-04 23:18:39 -0700319 }
320 }
321
322 return NULL;
323 }
324
325 // --------------------------------------------------------------------
326
327 /**
328 * Set HTTP Status Header
329 *
330 * As of version 1.7.2, this is an alias for common function
331 * set_status_header().
332 *
333 * @param int $code Status code (default: 200)
334 * @param string $text Optional message
335 * @return CI_Output
336 */
337 public function set_status_header($code = 200, $text = '')
338 {
339 set_status_header($code, $text);
340 return $this;
341 }
342
343 // --------------------------------------------------------------------
344
345 /**
346 * Enable/disable Profiler
347 *
348 * @param bool $val TRUE to enable or FALSE to disable
349 * @return CI_Output
350 */
351 public function enable_profiler($val = TRUE)
352 {
353 $this->enable_profiler = is_bool($val) ? $val : TRUE;
354 return $this;
355 }
356
357 // --------------------------------------------------------------------
358
359 /**
360 * Set Profiler Sections
361 *
362 * Allows override of default/config settings for
363 * Profiler section display.
364 *
365 * @param array $sections Profiler sections
366 * @return CI_Output
367 */
368 public function set_profiler_sections($sections)
369 {
370 if (isset($sections['query_toggle_count']))
371 {
372 $this->_profiler_sections['query_toggle_count'] = (int) $sections['query_toggle_count'];
373 unset($sections['query_toggle_count']);
374 }
375
376 foreach ($sections as $section => $enable)
377 {
378 $this->_profiler_sections[$section] = ($enable !== FALSE);
379 }
380
381 return $this;
382 }
383
384 // --------------------------------------------------------------------
385
386 /**
387 * Set Cache
388 *
Andrey Andreev22df06b2016-01-20 12:10:08 +0200389 * @param int $time Cache expiration time in minutes
Michael Dodge362b8002013-01-04 23:18:39 -0700390 * @return CI_Output
391 */
392 public function cache($time)
393 {
394 $this->cache_expiration = is_numeric($time) ? $time : 0;
395 return $this;
396 }
397
398 // --------------------------------------------------------------------
399
400 /**
401 * Display Output
402 *
natepizzle21432ab2015-02-03 16:25:42 -0600403 * Processes and sends finalized output data to the browser along
Michael Dodge362b8002013-01-04 23:18:39 -0700404 * with any server headers and profile data. It also stops benchmark
405 * timers so the page rendering speed and memory usage can be shown.
406 *
407 * Note: All "view" data is automatically put into $this->final_output
408 * by controller class.
409 *
410 * @uses CI_Output::$final_output
411 * @param string $output Output data override
412 * @return void
413 */
414 public function _display($output = '')
415 {
Andrey Andreevc26b9eb2014-02-24 11:31:36 +0200416 // Note: We use load_class() because we can't use $CI =& get_instance()
Michael Dodge362b8002013-01-04 23:18:39 -0700417 // since this function is sometimes called by the caching mechanism,
418 // which happens before the CI super object is available.
Andrey Andreevc26b9eb2014-02-24 11:31:36 +0200419 $BM =& load_class('Benchmark', 'core');
420 $CFG =& load_class('Config', 'core');
Michael Dodge362b8002013-01-04 23:18:39 -0700421
422 // Grab the super object if we can.
Andrey Andreev49e68de2013-02-21 16:30:55 +0200423 if (class_exists('CI_Controller', FALSE))
Michael Dodge362b8002013-01-04 23:18:39 -0700424 {
425 $CI =& get_instance();
426 }
427
428 // --------------------------------------------------------------------
429
430 // Set the output data
431 if ($output === '')
432 {
433 $output =& $this->final_output;
434 }
435
436 // --------------------------------------------------------------------
437
Michael Dodge362b8002013-01-04 23:18:39 -0700438 // Do we need to write a cache file? Only if the controller does not have its
439 // own _output() method and we are not dealing with a cache file, which we
440 // can determine by the existence of the $CI object above
441 if ($this->cache_expiration > 0 && isset($CI) && ! method_exists($CI, '_output'))
442 {
443 $this->_write_cache($output);
444 }
445
446 // --------------------------------------------------------------------
447
448 // Parse out the elapsed time and memory usage,
449 // then swap the pseudo-variables with the data
450
451 $elapsed = $BM->elapsed_time('total_execution_time_start', 'total_execution_time_end');
452
453 if ($this->parse_exec_vars === TRUE)
454 {
455 $memory = round(memory_get_usage() / 1024 / 1024, 2).'MB';
Michael Dodge362b8002013-01-04 23:18:39 -0700456 $output = str_replace(array('{elapsed_time}', '{memory_usage}'), array($elapsed, $memory), $output);
457 }
458
459 // --------------------------------------------------------------------
460
461 // Is compression requested?
Andrey Andreev155ee722014-01-10 15:50:54 +0200462 if (isset($CI) // This means that we're not serving a cache file, if we were, it would already be compressed
463 && $this->_compress_output === TRUE
Michael Dodge362b8002013-01-04 23:18:39 -0700464 && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
465 {
466 ob_start('ob_gzhandler');
467 }
468
469 // --------------------------------------------------------------------
470
471 // Are there any server headers to send?
472 if (count($this->headers) > 0)
473 {
474 foreach ($this->headers as $header)
475 {
476 @header($header[0], $header[1]);
477 }
478 }
479
480 // --------------------------------------------------------------------
481
482 // Does the $CI object exist?
483 // If not we know we are dealing with a cache file so we'll
484 // simply echo out the data and exit.
485 if ( ! isset($CI))
486 {
Andrey Andreev155ee722014-01-10 15:50:54 +0200487 if ($this->_compress_output === TRUE)
488 {
489 if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
490 {
491 header('Content-Encoding: gzip');
Andrey Andreev4e2cdec2016-10-28 14:19:08 +0300492 header('Content-Length: '.self::strlen($output));
Andrey Andreev155ee722014-01-10 15:50:54 +0200493 }
494 else
495 {
496 // User agent doesn't support gzip compression,
497 // so we'll have to decompress our cache
Andrey Andreev4e2cdec2016-10-28 14:19:08 +0300498 $output = gzinflate(self::substr($output, 10, -8));
Andrey Andreev155ee722014-01-10 15:50:54 +0200499 }
500 }
501
Michael Dodge362b8002013-01-04 23:18:39 -0700502 echo $output;
Andrey Andreev90726b82015-01-20 12:39:22 +0200503 log_message('info', 'Final output sent to browser');
Michael Dodge362b8002013-01-04 23:18:39 -0700504 log_message('debug', 'Total execution time: '.$elapsed);
505 return;
506 }
507
508 // --------------------------------------------------------------------
509
510 // Do we need to generate profile data?
511 // If so, load the Profile class and run it.
512 if ($this->enable_profiler === TRUE)
513 {
514 $CI->load->library('profiler');
515 if ( ! empty($this->_profiler_sections))
516 {
517 $CI->profiler->set_sections($this->_profiler_sections);
518 }
519
520 // If the output data contains closing </body> and </html> tags
521 // we will remove them and add them back after we insert the profile data
522 $output = preg_replace('|</body>.*?</html>|is', '', $output, -1, $count).$CI->profiler->run();
523 if ($count > 0)
524 {
525 $output .= '</body></html>';
526 }
527 }
528
529 // Does the controller contain a function named _output()?
530 // If so send the output there. Otherwise, echo it.
531 if (method_exists($CI, '_output'))
532 {
533 $CI->_output($output);
534 }
535 else
536 {
537 echo $output; // Send it to the browser!
538 }
539
Andrey Andreev90726b82015-01-20 12:39:22 +0200540 log_message('info', 'Final output sent to browser');
Michael Dodge362b8002013-01-04 23:18:39 -0700541 log_message('debug', 'Total execution time: '.$elapsed);
542 }
543
544 // --------------------------------------------------------------------
545
546 /**
547 * Write Cache
548 *
549 * @param string $output Output data to cache
550 * @return void
551 */
552 public function _write_cache($output)
553 {
554 $CI =& get_instance();
555 $path = $CI->config->item('cache_path');
556 $cache_path = ($path === '') ? APPPATH.'cache/' : $path;
557
558 if ( ! is_dir($cache_path) OR ! is_really_writable($cache_path))
559 {
560 log_message('error', 'Unable to write cache file: '.$cache_path);
561 return;
562 }
563
Andrey Andreev155ee722014-01-10 15:50:54 +0200564 $uri = $CI->config->item('base_url')
565 .$CI->config->item('index_page')
566 .$CI->uri->uri_string();
Michael Dodge362b8002013-01-04 23:18:39 -0700567
Andrey Andreev298e0052015-07-15 17:17:18 +0300568 if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
Andrey Andreeva704aa72014-12-04 12:37:07 +0200569 {
w0dendc29c6d2015-05-11 18:58:20 +0300570 if (is_array($cache_query_string))
571 {
572 $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string)));
573 }
574 else
575 {
576 $uri .= '?'.$_SERVER['QUERY_STRING'];
577 }
Andrey Andreeva704aa72014-12-04 12:37:07 +0200578 }
Stefano Mazzegaac41ca62014-12-03 11:55:47 +0100579
Michael Dodge362b8002013-01-04 23:18:39 -0700580 $cache_path .= md5($uri);
581
Andrey Andreev7cf682a2014-03-13 14:55:45 +0200582 if ( ! $fp = @fopen($cache_path, 'w+b'))
Michael Dodge362b8002013-01-04 23:18:39 -0700583 {
584 log_message('error', 'Unable to write cache file: '.$cache_path);
585 return;
586 }
587
Michael Dodge362b8002013-01-04 23:18:39 -0700588 if (flock($fp, LOCK_EX))
589 {
Andrey Andreev155ee722014-01-10 15:50:54 +0200590 // If output compression is enabled, compress the cache
591 // itself, so that we don't have to do that each time
592 // we're serving it
593 if ($this->_compress_output === TRUE)
594 {
595 $output = gzencode($output);
596
597 if ($this->get_header('content-type') === NULL)
598 {
599 $this->set_content_type($this->mime_type);
600 }
601 }
602
603 $expire = time() + ($this->cache_expiration * 60);
604
605 // Put together our serialized info.
606 $cache_info = serialize(array(
607 'expire' => $expire,
608 'headers' => $this->headers
609 ));
610
Andrey Andreevd8b1ad32014-01-15 17:42:52 +0200611 $output = $cache_info.'ENDCI--->'.$output;
612
Andrey Andreev4e2cdec2016-10-28 14:19:08 +0300613 for ($written = 0, $length = self::strlen($output); $written < $length; $written += $result)
Andrey Andreevd8b1ad32014-01-15 17:42:52 +0200614 {
Andrey Andreev4e2cdec2016-10-28 14:19:08 +0300615 if (($result = fwrite($fp, self::substr($output, $written))) === FALSE)
Andrey Andreevd8b1ad32014-01-15 17:42:52 +0200616 {
617 break;
618 }
619 }
620
Michael Dodge362b8002013-01-04 23:18:39 -0700621 flock($fp, LOCK_UN);
622 }
623 else
624 {
625 log_message('error', 'Unable to secure a file lock for file at: '.$cache_path);
626 return;
627 }
Andrey Andreev155ee722014-01-10 15:50:54 +0200628
Michael Dodge362b8002013-01-04 23:18:39 -0700629 fclose($fp);
Michael Dodge362b8002013-01-04 23:18:39 -0700630
Andrey Andreevd8b1ad32014-01-15 17:42:52 +0200631 if (is_int($result))
632 {
Andrey Andreev45965742014-08-27 20:40:11 +0300633 chmod($cache_path, 0640);
Andrey Andreevd8b1ad32014-01-15 17:42:52 +0200634 log_message('debug', 'Cache file written: '.$cache_path);
Michael Dodge362b8002013-01-04 23:18:39 -0700635
Andrey Andreevd8b1ad32014-01-15 17:42:52 +0200636 // Send HTTP cache-control headers to browser to match file cache settings.
637 $this->set_cache_header($_SERVER['REQUEST_TIME'], $expire);
638 }
639 else
640 {
641 @unlink($cache_path);
642 log_message('error', 'Unable to write the complete cache content at: '.$cache_path);
643 }
Michael Dodge362b8002013-01-04 23:18:39 -0700644 }
645
646 // --------------------------------------------------------------------
647
648 /**
649 * Update/serve cached output
650 *
651 * @uses CI_Config
652 * @uses CI_URI
653 *
654 * @param object &$CFG CI_Config class instance
655 * @param object &$URI CI_URI class instance
656 * @return bool TRUE on success or FALSE on failure
657 */
658 public function _display_cache(&$CFG, &$URI)
659 {
660 $cache_path = ($CFG->item('cache_path') === '') ? APPPATH.'cache/' : $CFG->item('cache_path');
661
662 // Build the file path. The file name is an MD5 hash of the full URI
Andrey Andreev33572252014-12-03 20:19:38 +0200663 $uri = $CFG->item('base_url').$CFG->item('index_page').$URI->uri_string;
Andrey Andreeva704aa72014-12-04 12:37:07 +0200664
Andrey Andreev298e0052015-07-15 17:17:18 +0300665 if (($cache_query_string = $CFG->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
Andrey Andreeva704aa72014-12-04 12:37:07 +0200666 {
w0dendc29c6d2015-05-11 18:58:20 +0300667 if (is_array($cache_query_string))
668 {
669 $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string)));
670 }
671 else
672 {
673 $uri .= '?'.$_SERVER['QUERY_STRING'];
674 }
Andrey Andreeva704aa72014-12-04 12:37:07 +0200675 }
Stefano Mazzegaac41ca62014-12-03 11:55:47 +0100676
Michael Dodge362b8002013-01-04 23:18:39 -0700677 $filepath = $cache_path.md5($uri);
678
Andrey Andreev7cf682a2014-03-13 14:55:45 +0200679 if ( ! file_exists($filepath) OR ! $fp = @fopen($filepath, 'rb'))
Michael Dodge362b8002013-01-04 23:18:39 -0700680 {
681 return FALSE;
682 }
683
684 flock($fp, LOCK_SH);
685
686 $cache = (filesize($filepath) > 0) ? fread($fp, filesize($filepath)) : '';
687
688 flock($fp, LOCK_UN);
689 fclose($fp);
690
Eric Robertsc90e67e2013-01-11 21:20:54 -0600691 // Look for embedded serialized file info.
692 if ( ! preg_match('/^(.*)ENDCI--->/', $cache, $match))
Michael Dodge362b8002013-01-04 23:18:39 -0700693 {
694 return FALSE;
695 }
Purwandi5dc6d512013-01-19 17:43:08 +0700696
Eric Robertsc90e67e2013-01-11 21:20:54 -0600697 $cache_info = unserialize($match[1]);
698 $expire = $cache_info['expire'];
Michael Dodge362b8002013-01-04 23:18:39 -0700699
Ivan Tcholakovfd1bc222015-04-23 23:21:08 +0300700 $last_modified = filemtime($filepath);
Michael Dodge362b8002013-01-04 23:18:39 -0700701
702 // Has the file expired?
703 if ($_SERVER['REQUEST_TIME'] >= $expire && is_really_writable($cache_path))
704 {
705 // If so we'll delete it.
706 @unlink($filepath);
707 log_message('debug', 'Cache file has expired. File deleted.');
708 return FALSE;
709 }
710 else
711 {
712 // Or else send the HTTP cache control headers.
713 $this->set_cache_header($last_modified, $expire);
714 }
Purwandi5dc6d512013-01-19 17:43:08 +0700715
Eric Robertsc90e67e2013-01-11 21:20:54 -0600716 // Add headers from cache file.
717 foreach ($cache_info['headers'] as $header)
718 {
719 $this->set_header($header[0], $header[1]);
720 }
Michael Dodge362b8002013-01-04 23:18:39 -0700721
722 // Display the cache
Andrey Andreev4e2cdec2016-10-28 14:19:08 +0300723 $this->_display(self::substr($cache, self::strlen($match[0])));
Michael Dodge362b8002013-01-04 23:18:39 -0700724 log_message('debug', 'Cache file is current. Sending it to browser.');
725 return TRUE;
726 }
727
728 // --------------------------------------------------------------------
729
730 /**
731 * Delete cache
732 *
733 * @param string $uri URI string
734 * @return bool
735 */
736 public function delete_cache($uri = '')
737 {
738 $CI =& get_instance();
739 $cache_path = $CI->config->item('cache_path');
740 if ($cache_path === '')
741 {
742 $cache_path = APPPATH.'cache/';
743 }
744
745 if ( ! is_dir($cache_path))
746 {
747 log_message('error', 'Unable to find cache path: '.$cache_path);
748 return FALSE;
749 }
750
751 if (empty($uri))
752 {
753 $uri = $CI->uri->uri_string();
Andrey Andreeva704aa72014-12-04 12:37:07 +0200754
Andrey Andreev298e0052015-07-15 17:17:18 +0300755 if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
Andrey Andreeva704aa72014-12-04 12:37:07 +0200756 {
w0dendc29c6d2015-05-11 18:58:20 +0300757 if (is_array($cache_query_string))
758 {
759 $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string)));
760 }
761 else
762 {
763 $uri .= '?'.$_SERVER['QUERY_STRING'];
764 }
Andrey Andreeva704aa72014-12-04 12:37:07 +0200765 }
Michael Dodge362b8002013-01-04 23:18:39 -0700766 }
767
w0den0b978dd2015-05-02 17:53:33 +0300768 $cache_path .= md5($CI->config->item('base_url').$CI->config->item('index_page').ltrim($uri, '/'));
Michael Dodge362b8002013-01-04 23:18:39 -0700769
770 if ( ! @unlink($cache_path))
771 {
772 log_message('error', 'Unable to delete cache file for '.$uri);
773 return FALSE;
774 }
775
776 return TRUE;
777 }
778
779 // --------------------------------------------------------------------
780
781 /**
782 * Set Cache Header
783 *
784 * Set the HTTP headers to match the server-side file cache settings
785 * in order to reduce bandwidth.
786 *
787 * @param int $last_modified Timestamp of when the page was last modified
788 * @param int $expiration Timestamp of when should the requested page expire from cache
789 * @return void
790 */
791 public function set_cache_header($last_modified, $expiration)
792 {
793 $max_age = $expiration - $_SERVER['REQUEST_TIME'];
794
795 if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $last_modified <= strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']))
796 {
797 $this->set_status_header(304);
798 exit;
799 }
800 else
801 {
802 header('Pragma: public');
Andrey Andreev3ca060a2013-11-27 16:30:31 +0200803 header('Cache-Control: max-age='.$max_age.', public');
Michael Dodge362b8002013-01-04 23:18:39 -0700804 header('Expires: '.gmdate('D, d M Y H:i:s', $expiration).' GMT');
805 header('Last-modified: '.gmdate('D, d M Y H:i:s', $last_modified).' GMT');
806 }
807 }
808
Andrey Andreev4e2cdec2016-10-28 14:19:08 +0300809 // --------------------------------------------------------------------
810
811 /**
812 * Byte-safe strlen()
813 *
814 * @param string $str
815 * @return int
816 */
817 protected static function strlen($str)
818 {
819 return (self::$func_override)
820 ? mb_strlen($str, '8bit')
821 : strlen($str);
822 }
823
824 // --------------------------------------------------------------------
825
826 /**
827 * Byte-safe substr()
828 *
829 * @param string $str
830 * @param int $start
831 * @param int $length
832 * @return string
833 */
834 protected static function substr($str, $start, $length = NULL)
835 {
836 if (self::$func_override)
837 {
838 // mb_substr($str, $start, null, '8bit') returns an empty
839 // string on PHP 5.3
840 isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start);
841 return mb_substr($str, $start, $length, '8bit');
842 }
843
844 return isset($length)
845 ? substr($str, $start, $length)
846 : substr($str, $start);
847 }
Michael Dodge362b8002013-01-04 23:18:39 -0700848}